├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── TODO.txt ├── pom.xml └── src ├── main └── java │ └── ch │ └── digitalfondue │ └── npjt │ ├── AffectedRowCountAndKey.java │ ├── AutoGeneratedKey.java │ ├── Bind.java │ ├── ConstructorAnnotationRowMapper.java │ ├── EnableNpjt.java │ ├── QueriesOverride.java │ ├── Query.java │ ├── QueryFactory.java │ ├── QueryOverride.java │ ├── QueryRepository.java │ ├── QueryType.java │ ├── RepositoriesDefinitionRegistrar.java │ └── mapper │ ├── ColumnMapper.java │ ├── ColumnMapperFactory.java │ ├── DefaultMapper.java │ ├── EnumMapper.java │ ├── InstantMapper.java │ ├── LocalDateMapper.java │ ├── LocalDateTimeMapper.java │ ├── ParameterConverter.java │ └── ZonedDateTimeMapper.java └── test └── java └── ch └── digitalfondue └── npjt ├── ConstructorAnnotationRowMapperTest.java ├── QueryFactoryTest.java ├── QueryRepositoryScannerTest.java ├── QueryScannerConfiguration.java ├── TestJdbcConfiguration.java ├── mapper ├── DefaultMapperTest.java ├── EnumMapperTest.java └── ZonedDateTimeMapperTest.java └── query ├── AutogeneratedKeyQueriesTest.java ├── BooleanQueriesTest.java ├── CustomJSONQueriesTest.java ├── DateTimeQueriesTest.java ├── EnumQueriesTest.java ├── QueryRepo.java ├── SimpleQueriesTest.java ├── customfactory └── CustomJSONQueriesWithCustomQueryFactoryTest.java └── deeper └── QueryRepo2.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | java: ['1.8','11','12','13', '14'] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up JDK ${{matrix.java}} 23 | uses: actions/setup-java@v1 24 | with: 25 | java-version: ${{matrix.java}} 26 | - name: Maven -v 27 | run: mvn -v 28 | - name: Cache Maven packages 29 | uses: actions/cache@v2 30 | with: 31 | path: ~/.m2 32 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 33 | restore-keys: ${{ runner.os }}-m2 34 | - name: Build with Maven 35 | run: mvn -B package --file pom.xml 36 | # don't work at the moment it seems, see https://github.com/trautonen/coveralls-maven-plugin/issues/136 37 | # - name: Coverage 38 | # run: mvn jacoco:report coveralls:report 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .classpath 3 | .project 4 | .settings/ 5 | .idea/ 6 | .gradle/ 7 | build/ 8 | *.iml 9 | *.ipr 10 | *.iws 11 | /build/ 12 | .gradle/ 13 | deploy*.sh 14 | custom.jvmargs 15 | classes/ 16 | /target/ 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | npjt-extra 2 | ========== 3 | 4 | [![Build Status](https://img.shields.io/github/workflow/status/digitalfondue/npjt-extra/Java%20CI%20with%20Maven)](https://github.com/digitalfondue/npjt-extra/actions?query=workflow%3A%22Java+CI+with+Maven%22) 5 | [![Maven Central](https://img.shields.io/maven-central/v/ch.digitalfondue.npjt-extra/npjt-extra.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22ch.digitalfondue.npjt-extra%22%20AND%20a%3A%22npjt-extra%22) 6 | 7 | A small layer over Spring's NamedParameterJdbcTemplate, it provide a similar interface as: 8 | 9 | - the jdbi object api (http://jdbi.org/#__sqlquery) 10 | - the spring-data-jpa named parameters api (http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.named-parameters) 11 | 12 | With some extra goodies. Requires java8 or later. 13 | 14 | Note: this project predates spring-data-jdbc. 15 | 16 | See the Use and examples section. 17 | 18 | ## Download 19 | 20 | ### Maven 21 | 22 | ```xml 23 | 24 | ch.digitalfondue.npjt-extra 25 | npjt-extra 26 | 2.0.4 27 | 28 | ``` 29 | 30 | ### Gradle 31 | 32 | ``` 33 | compile "ch.digitalfondue.npjt-extra:npjt-extra:2.0.4" 34 | ``` 35 | 36 | ## Use and examples 37 | 38 | npjt-extra is composed of 3 parts: 39 | 40 | - a default RowMapper (annotation based) 41 | - an interface based query repository 42 | - the configuration classes 43 | 44 | ### RowMapping definition 45 | 46 | For mapping a row to a class, npjt-extra offer a default row mapper that require the following restriction: 47 | 48 | - the class must have only **one public constructor** (this restriction could be lifted off). 49 | - each of the constructor argument must have a @Column annotation that map the column name to the parameter. 50 | 51 | This constructor approach is for promoting an immutable model. 52 | 53 | *IF* you cannot map using this strategy, you can specify the mapper class in the @Query and @QueryOverride annotation. 54 | Your class must implement `org.springframework.jdbc.core.RowMapper`. 55 | See below in the "Basic use" section. 56 | 57 | Example: 58 | 59 | ```java 60 | 61 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 62 | 63 | public static class Conf { 64 | 65 | final String key; 66 | final String value; 67 | 68 | public Conf(@Column("CONF_KEY") String key, @Column("CONF_VALUE") String value) { 69 | this.key = key; 70 | this.value = value; 71 | } 72 | } 73 | 74 | ``` 75 | 76 | As you can probably guess, this class will map a ResultSet containing 2 column named "CONF_KEY" and "CONF_VALUE". 77 | 78 | ### Query repository definition 79 | 80 | npjt-extra generate a proxy from the interface defined by the user. 81 | 82 | The rules are simple: 83 | 84 | - you can define a method without parameter that return NamedParameterJdbcTemplate: the proxy will return the underlying NamedParameterJdbcTemplate. 85 | - you can define default methods 86 | - all the others methods must contain the @Query(...) annotation 87 | 88 | #### Basic use 89 | 90 | A basic "query repository" will be similar to (using the Conf class defined before): 91 | 92 | ```java 93 | 94 | import java.util.List; 95 | import ch.digitalfondue.npjt.Bind; 96 | import ch.digitalfondue.npjt.Query; 97 | 98 | public interface MySimpleQueries { 99 | 100 | /** insert a key,value pair, return the number of affected rows */ 101 | @Query("INSERT INTO LA_CONF(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 102 | int insertValue(@Bind("key") String key, @Bind("value") String value); 103 | 104 | /** 105 | * find a single element, as the underlying NamedParameterJdbcTemplate, 106 | * it will launch an exception if there are 0 or more than 1 object 107 | */ 108 | @Query("SELECT * FROM LA_CONF WHERE CONF_KEY = :key") 109 | Conf findByKey(@Bind("key") String key); 110 | 111 | /** It will map multiple values too */ 112 | @Query("SELECT * FROM LA_CONF") 113 | List findAll(); 114 | 115 | 116 | /** You can override the default mapper and specify your own */ 117 | @Query(value = "SELECT * FROM LA_CONF", mapper = MyConfMapper.class) 118 | List findAllCustomMapper(); 119 | 120 | 121 | /** You can search "simple" types too if they are supported by spring jdbc */ 122 | @Query("SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = 'BLA'") 123 | String findBla(); 124 | 125 | /** You can search Lists of "simple" types too if they are supported by spring jdbc */ 126 | @Query("SELECT CONF_KEY FROM LA_CONF") 127 | List findAllKeys(); 128 | 129 | } 130 | 131 | ``` 132 | 133 | The custom mapper specified in the annotation of the method findAllCustomMapper must implement `org.springframework.jdbc.core.RowMapper`. 134 | 135 | #### Query override 136 | 137 | If you want to support multiple DB which have some slightly different syntax you can override a query: 138 | 139 | For example: 140 | 141 | ```java 142 | 143 | import ch.digitalfondue.npjt.Query; 144 | import ch.digitalfondue.npjt.QueryOverride; 145 | import ch.digitalfondue.npjt.QueriesOverride; 146 | 147 | public interface QueryTest { 148 | @Query("SELECT * FROM LA_CONF") 149 | @QueriesOverride({ 150 | @QueryOverride(db = "MYSQL", value = "SELECT * FROM LA_CONF_MYSQL"), 151 | @QueryOverride(db = "PGSQL", value = "SELECT * FROM LA_CONF_PGSQL") 152 | }) 153 | List findAll(); 154 | } 155 | ``` 156 | 157 | When creating the ch.digitalfondue.npjt.QueryFactory that will generate the repositories, you must specify 158 | 2 parameters: first the **DB** name and second the DataSource. 159 | 160 | 161 | If the db name match the db parameter specified in a @QueryOverride, the associated String value will be used. 162 | 163 | *Note*: if you are defining a custom mapper in the @Query annotation, _you_ must specify it in the @QueryOverride too! 164 | 165 | #### Fetch generated keys 166 | 167 | If you have a table with an auto generated key and you want to get the value after your insert, you can set the return 168 | type to `AffectedRowCountAndKey`. 169 | 170 | Example: 171 | 172 | You have a table (HSQLDB) defined as: 173 | 174 | ```sql 175 | CREATE TABLE LA_AUTO (ID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL, VALUE CLOB NOT NULL); 176 | ``` 177 | 178 | In your QueryRepository you define: 179 | 180 | ```java 181 | 182 | public interface AutogeneratedKeyQueries { 183 | 184 | @Query("INSERT INTO LA_AUTO(VALUE) VALUES (:value)") 185 | AffectedRowCountAndKey insert(@Bind("value") String value); 186 | 187 | } 188 | ``` 189 | 190 | The returned AffectedRowCountAndKey will contain the ID of the inserted element. You can see in action in the test AutogeneratedKeyQueriesTest.java 191 | 192 | Notes: 193 | 194 | - in some cases (for example if you are using pgsql), you must define the column name of the generated identifier with the annotation `@AutoGeneratedKey("COLUMN_NAME")` as the DB even though there is a single generated key it will return more than one. 195 | - it's currently a basic implementation that support the simplest use cases: pull request or test cases that highlight a missing feature are welcome! 196 | 197 | #### Query templates 198 | 199 | If you only require to generate a query string which depend from the db type, you can define a query template. 200 | 201 | You need to define the type in the @Query annotation to QueryType.TEMPLATE and the return type of the query as String. 202 | 203 | For example: 204 | 205 | ```java 206 | 207 | public interface QueryTest { 208 | 209 | @Query(type = QueryType.TEMPLATE, value = "MY_TEMPLATE") 210 | @QueriesOverride({ 211 | @QueryOverride(db = "MYSQL", value = "SELECT * FROM MY_TEMPLATE_MYSQL"), 212 | @QueryOverride(db = "PGSQL", value = "SELECT * FROM MY_TEMPLATE_PGSQL") 213 | }) 214 | String template(); 215 | } 216 | ``` 217 | 218 | Calling template() will return "MY_TEMPLATE" (or the overridden values). 219 | 220 | ##### Optional 221 | 222 | You can wrap the returned object in a Optional. For example: 223 | 224 | ```java 225 | @Query("SELECT * FROM LA_CONF WHERE CONF_KEY = :key") 226 | Optional findByKey(@Bind("key") String key); 227 | ``` 228 | 229 | Will work as expected. If the query return more than one object it will launch an exception like the unwrapped 230 | version. 231 | 232 | ##### Default methods in the interface 233 | 234 | You can add default methods too, for example, if you need some custom query directly with 235 | the NamedParameterJdbcTemplate: 236 | 237 | ```java 238 | public interface MySimpleQueries { 239 | 240 | 241 | @Query("INSERT INTO LA_CONF(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 242 | int insertValue(@Bind("key") String key, @Bind("value") String value); 243 | 244 | /** any method that return NamedParameterJdbcTemplate and has 0 arguments will return the underlying NamedParameterJdbcTemplate*/ 245 | NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(); 246 | 247 | /** here your default method */ 248 | default String defaultMethod(String key) { 249 | return getNamedParameterJdbcTemplate() 250 | .queryForObject("SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = :key", Collections.singletonMap("key", key), String.class); 251 | } 252 | } 253 | ``` 254 | 255 | ##### LocalDate, LocalDateTime, Instant support 256 | 257 | npjt-extra support out of the box LocalDate, LocalDateTime and Instant both as a parameter of a interface method and as a mapped value. 258 | 259 | 260 | ### Configuration 261 | 262 | You can configure it in 2 ways. 263 | 264 | #### Using @EnableNpjt annotation 265 | 266 | First you need to annotate your query repository with the ch.digitalfondue.npjt.QueryRepository annotation. 267 | 268 | Note, you will need a DataSource configured. 269 | 270 | Then you only need to configure the packages to scan: 271 | 272 | ```java 273 | 274 | /** scan the packages "ch.digitalfondue.npjt.query" and "ch.digitalfondue.npjt.columnmapper" */ 275 | @EnableNpjt(basePackages = {"ch.digitalfondue.npjt.query", "ch.digitalfondue.npjt.columnmapper"}) 276 | public class MyConfig2 { 277 | } 278 | 279 | ``` 280 | 281 | #### Manual instantiation 282 | 283 | Using the JavaConfig: 284 | 285 | ```java 286 | 287 | public class MyConfig { 288 | 289 | /** instantiate the interface MySimpleQueries */ 290 | /** the db type could be another parameter :) */ 291 | @Bean 292 | public MySimpleQueries getMySimpleQueries(DataSource dataSource) { 293 | return QueryFactory.from(MySimpleQueries.class, "HSQLDB", dataSource); 294 | } 295 | 296 | } 297 | 298 | ``` 299 | 300 | 301 | 302 | All the annotated interfaces will be available in your spring context. 303 | 304 | ### Data type mapping 305 | 306 | By default, npjt-extra has the following input parameters/result set mapping facilities enabled: 307 | 308 | - a default parameter/result set mapper which use the same logic as the one from the jdbctemplate 309 | - a enum mapper that convert from/to a string representation 310 | - support ZonedDateTimeMapper, LocalDate, LocalDateTime and Instant 311 | 312 | You can add new mappers by exposing as a bean a `List` and a `List` (see example at: https://github.com/digitalfondue/npjt-extra/blob/master/src/test/java/ch/digitalfondue/npjt/query/CustomJSONQueriesTest.java#L125). 313 | 314 | If you want to configure from an empty list, you can define your custom `QueryFactory`: see full example at https://github.com/digitalfondue/npjt-extra/blob/master/src/test/java/ch/digitalfondue/npjt/query/customfactory/CustomJSONQueriesWithCustomQueryFactoryTest.java#L131 315 | where instead of calling `super.getDefaultFactories();` you can begin from an empty List. 316 | 317 | Then when using the @EnableNpjt you will need to specify the Factory: 318 | 319 | `@EnableNpjt(queryFactory = CustomQueryFactory.class, basePackages = {"ch.digitalfondue.npjt.query.customfactory"})` 320 | 321 | 322 | For both mappers, the application order is defined by the `order()` method. The smallest the `int` returned, the higher the priority. 323 | 324 | 325 | #### Input parameters mapping 326 | 327 | You must implement the `ch.digitalfondue.npjt.mapper.ParameterConverter` interface and register the converter in the queryFactory calling the `QueryFactory.addParameterConverters(ParameterConverter parameterConverter)` method. 328 | 329 | #### Result set objects mapping 330 | 331 | You must implement the `ch.digitalfondue.npjt.mapper.ColumnMapperFactory` interface and register the converter in the queryFactory calling the `addColumnMapperFactory(ColumnMapperFactory columnMapperFactory)` method. 332 | 333 | 334 | ## Javadoc 335 | 336 | - http://javadoc.io/doc/ch.digitalfondue.npjt-extra/npjt-extra/ 337 | 338 | ## License 339 | 340 | The library is under The Apache Software License, Version 2.0 341 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | - Expose the type mapping infrastructure for the custom mappers (if they implement a specific subtype of RowMapper) 5 | - a refactor on this part will be necessary 6 | 7 | - @AutoGeneratedKey has some shortcoming: 8 | - no automatic type conversion with the current infrastructure of ColumnMapper 9 | - single field name for all DB 10 | 11 | - Support stored procedures in the annotation(?) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 9 8 | 9 | 10 | ch.digitalfondue.npjt-extra 11 | npjt-extra 12 | 2.0.5-SNAPSHOT 13 | jar 14 | 2015 15 | 16 | npjt-extra 17 | https://github.com/digitalfondue/npjt-extra 18 | 19 | 20 | 21 | The Apache Software License, Version 2.0 22 | http://www.apache.org/licenses/LICENSE-2.0.txt 23 | repo 24 | 25 | 26 | 27 | 28 | scm:git:git@github.com:digitalfondue/npjt-extra.git 29 | scm:git:git@github.com:digitalfondue/npjt-extra.git 30 | https://github.com/digitalfondue/npjt-extra.git 31 | HEAD 32 | 33 | 34 | 35 | 36 | Sylvain Jermini 37 | sylvain.jermini@syjer.com 38 | digitalfondue 39 | http://digitalfondue.ch 40 | 41 | 42 | 43 | 44 | UTF-8 45 | 5.2.22.RELEASE 46 | 47 | 48 | 49 | 50 | org.springframework 51 | spring-jdbc 52 | ${org.springframework.version} 53 | 54 | 55 | org.springframework 56 | spring-context 57 | ${org.springframework.version} 58 | 59 | 60 | 61 | 62 | 63 | junit 64 | junit 65 | 4.13.1 66 | test 67 | 68 | 69 | org.mockito 70 | mockito-all 71 | 1.10.18 72 | test 73 | 74 | 75 | org.hsqldb 76 | hsqldb 77 | 2.3.1 78 | test 79 | 80 | 81 | org.postgresql 82 | postgresql 83 | 42.4.3 84 | test 85 | 86 | 87 | org.springframework 88 | spring-test 89 | ${org.springframework.version} 90 | test 91 | 92 | 93 | com.google.code.gson 94 | gson 95 | 2.8.9 96 | test 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-javadoc-plugin 105 | 2.10.3 106 | 107 | 8 108 | -Xdoclint:none 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-compiler-plugin 114 | 3.2 115 | 116 | 1.8 117 | 1.8 118 | 1.8 119 | 1.8 120 | 121 | -Xlint:deprecation 122 | -Xlint:unchecked 123 | 124 | 125 | 126 | 148 | 149 | org.eluder.coveralls 150 | coveralls-maven-plugin 151 | 4.3.0 152 | 153 | 154 | javax.xml.bind 155 | jaxb-api 156 | 2.3.1 157 | 158 | 159 | 160 | 161 | org.jacoco 162 | jacoco-maven-plugin 163 | 0.8.5 164 | 165 | 166 | prepare-agent 167 | 168 | prepare-agent 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | org.apache.maven.plugins 180 | maven-pmd-plugin 181 | 3.3 182 | 183 | 184 | org.codehaus.mojo 185 | findbugs-maven-plugin 186 | 3.0.0 187 | 188 | 189 | org.codehaus.mojo 190 | jdepend-maven-plugin 191 | 2.0 192 | 193 | 194 | 195 | 196 | 197 | 198 | sign-artifacts 199 | 200 | 201 | sign 202 | true 203 | 204 | 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-gpg-plugin 210 | 211 | 212 | sign-artifacts 213 | verify 214 | 215 | sign 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/AffectedRowCountAndKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | public class AffectedRowCountAndKey { 19 | 20 | private final int affectedRowCount; 21 | private final T key; 22 | 23 | public AffectedRowCountAndKey(int affectedRowCount, T key) { 24 | this.affectedRowCount = affectedRowCount; 25 | this.key = key; 26 | } 27 | 28 | public int getAffectedRowCount() { 29 | return affectedRowCount; 30 | } 31 | public T getKey() { 32 | return key; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/AutoGeneratedKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * Marker annotation for retrieval of the latest auto-generated key. 25 | * All the method annotated with @AutoGeneratedKey must return a {@code AffectedRowCountAndKey}. 26 | */ 27 | @Target(ElementType.METHOD) 28 | @Retention(RetentionPolicy.RUNTIME) 29 | public @interface AutoGeneratedKey { 30 | String value(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/Bind.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @Target(ElementType.PARAMETER) 25 | public @interface Bind { 26 | String value(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/ConstructorAnnotationRowMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | import java.lang.reflect.Constructor; 24 | import java.sql.ResultSet; 25 | import java.sql.SQLException; 26 | import java.util.Collection; 27 | 28 | import org.springframework.jdbc.core.RowMapper; 29 | import org.springframework.util.Assert; 30 | 31 | import ch.digitalfondue.npjt.mapper.ColumnMapper; 32 | import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; 33 | 34 | public class ConstructorAnnotationRowMapper implements RowMapper { 35 | 36 | private final Constructor con; 37 | private final ColumnMapper[] mappedColumn; 38 | 39 | /** 40 | * Check if the given class has the correct form. 41 | * 42 | *
    43 | *
  • must have exactly one public constructor.
  • 44 | *
  • must at least have one parameter.
  • 45 | *
  • all the parameters must be annotated with @Column annotation.
  • 46 | *
47 | * 48 | * @param clazz 49 | * @return 50 | */ 51 | public static boolean hasConstructorInTheCorrectForm(Class clazz) { 52 | 53 | if (clazz.getConstructors().length != 1) { 54 | return false; 55 | } 56 | 57 | Constructor con = clazz.getConstructors()[0]; 58 | 59 | if (con.getParameterTypes().length == 0) { 60 | return false; 61 | } 62 | 63 | Annotation[][] parameterAnnotations = con.getParameterAnnotations(); 64 | for (Annotation[] as : parameterAnnotations) { 65 | if (!hasColumnAnnotation(as)) { 66 | return false; 67 | } 68 | } 69 | 70 | return true; 71 | } 72 | 73 | private static boolean hasColumnAnnotation(Annotation[] as) { 74 | if (as == null || as.length == 0) { 75 | return false; 76 | } 77 | for (Annotation a : as) { 78 | if (a.annotationType().isAssignableFrom(Column.class)) { 79 | return true; 80 | } 81 | } 82 | 83 | return false; 84 | } 85 | 86 | @SuppressWarnings("unchecked") 87 | public ConstructorAnnotationRowMapper(Class clazz, Collection columnMapperFactories) { 88 | int constructorCount = clazz.getConstructors().length; 89 | Assert.isTrue(constructorCount == 1, "The class " + clazz.getName() 90 | + " must have exactly one public constructor, " 91 | + constructorCount + " are present"); 92 | 93 | con = (Constructor) clazz.getConstructors()[0]; 94 | mappedColumn = from(clazz, con.getParameterAnnotations(), con.getParameterTypes(), columnMapperFactories); 95 | } 96 | 97 | @Override 98 | public T mapRow(ResultSet rs, int rowNum) throws SQLException { 99 | Object[] vals = new Object[mappedColumn.length]; 100 | 101 | for(int i = 0; i < mappedColumn.length; i++) { 102 | vals[i] = mappedColumn[i].getObject(rs); 103 | } 104 | 105 | 106 | try { 107 | return con.newInstance(vals); 108 | } catch (ReflectiveOperationException e) { 109 | throw new SQLException(e); 110 | } catch (IllegalArgumentException e) { 111 | throw new SQLException( 112 | "type mismatch between the expected one from the construct and the one passed," 113 | + " check 1: some values are null and passed to primitive types 2: incompatible numeric types", 114 | e); 115 | } 116 | } 117 | 118 | private static ColumnMapper[] from(Class clazz, Annotation[][] annotations, Class[] paramTypes, Collection columnMapperFactories) { 119 | ColumnMapper[] res = new ColumnMapper[annotations.length]; 120 | for (int i = 0; i < annotations.length; i++) { 121 | res[i] = findColumnAnnotationValue(clazz, i, annotations[i], paramTypes[i], columnMapperFactories); 122 | } 123 | return res; 124 | } 125 | 126 | private static ColumnMapper findColumnAnnotationValue(Class clazz, 127 | int position, Annotation[] annotations, Class paramType, Collection columnMapperFactories) { 128 | 129 | for (Annotation a : annotations) { 130 | if (Column.class.isAssignableFrom(a.annotationType())) { 131 | String name = ((Column) a).value(); 132 | for(ColumnMapperFactory factory : columnMapperFactories) { 133 | if(factory.accept(paramType, annotations)) { 134 | return factory.build(name, paramType); 135 | } 136 | } 137 | throw new IllegalStateException( 138 | "Did not found any matching ColumnMapperFactory for class: " 139 | + clazz.getName() 140 | + " in constructor at position " + position); 141 | } 142 | } 143 | 144 | throw new IllegalStateException( 145 | "No annotation @Column found for class: " + clazz.getName() 146 | + " in constructor at position " + position); 147 | } 148 | 149 | 150 | 151 | @Retention(RetentionPolicy.RUNTIME) 152 | @Target(ElementType.PARAMETER) 153 | public @interface Column { 154 | /** 155 | * Column name 156 | * 157 | * @return 158 | */ 159 | String value(); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/EnableNpjt.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import org.springframework.context.annotation.Import; 19 | 20 | import java.lang.annotation.*; 21 | 22 | @Target(ElementType.TYPE) 23 | @Retention(value = RetentionPolicy.RUNTIME) 24 | @Inherited 25 | @Import(RepositoriesDefinitionRegistrar.class) 26 | public @interface EnableNpjt { 27 | String activeDB() default ""; 28 | String[] basePackages() default {}; 29 | Class queryFactory() default QueryFactory.class; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/QueriesOverride.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @Target(ElementType.METHOD) 25 | public @interface QueriesOverride { 26 | QueryOverride[] value(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/Query.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | import org.springframework.jdbc.core.RowMapper; 24 | 25 | @Retention(RetentionPolicy.RUNTIME) 26 | @Target(ElementType.METHOD) 27 | public @interface Query { 28 | String value(); 29 | QueryType type() default QueryType.EXECUTE; 30 | 31 | @SuppressWarnings("rawtypes") 32 | Class mapper() default ConstructorAnnotationRowMapper.class; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/QueryFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import ch.digitalfondue.npjt.mapper.*; 19 | import org.springframework.beans.factory.FactoryBean; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 22 | import org.springframework.util.ReflectionUtils; 23 | 24 | import javax.sql.DataSource; 25 | import java.lang.invoke.MethodHandle; 26 | import java.lang.invoke.MethodHandles; 27 | import java.lang.invoke.MethodType; 28 | import java.lang.reflect.Constructor; 29 | import java.lang.reflect.Method; 30 | import java.lang.reflect.Proxy; 31 | import java.util.*; 32 | 33 | public class QueryFactory implements FactoryBean { 34 | 35 | private final Class targetInterface; 36 | private final String activeDB; 37 | 38 | private DataSource dataSource; 39 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 40 | private List additionalColumnMapperFactories; 41 | private List additionalParameterConverters; 42 | 43 | public QueryFactory(Class targetInterface, String activeDB) { 44 | this.targetInterface = targetInterface; 45 | this.activeDB = activeDB; 46 | } 47 | 48 | public List getDefaultFactories() { 49 | return new ArrayList<>(Arrays.asList( 50 | new DefaultMapper.Factory(), 51 | new EnumMapper.Factory(), 52 | new LocalDateMapper.Factory(), 53 | new LocalDateTimeMapper.Factory(), 54 | new InstantMapper.Factory(), 55 | new ZonedDateTimeMapper.Factory()) 56 | ); 57 | } 58 | 59 | public List getDefaultParameterConverters() { 60 | return new ArrayList<>(Arrays.asList( 61 | new DefaultMapper.Converter(), 62 | new EnumMapper.Converter(), 63 | new LocalDateMapper.Converter(), 64 | new LocalDateTimeMapper.Converter(), 65 | new InstantMapper.Converter(), 66 | new ZonedDateTimeMapper.Converter()) 67 | ); 68 | } 69 | 70 | public static T from(Class clazz, String activeDB, DataSource dataSource) { 71 | return from(clazz, activeDB, dataSource, null, null); 72 | } 73 | 74 | public static T from(Class clazz, String activeDB, 75 | DataSource dataSource, 76 | List additionalColumnMappers, List additionalParameterConverters) { 77 | QueryFactory qf = new QueryFactory<>(clazz, activeDB); 78 | qf.setAdditionalColumnMapperFactories(additionalColumnMappers); 79 | qf.setAdditionalParameterConverters(additionalParameterConverters); 80 | qf.setDataSource(dataSource); 81 | return qf.getObject(); 82 | } 83 | 84 | @Override 85 | public T getObject() { 86 | return from(targetInterface); 87 | } 88 | 89 | @Override 90 | public Class getObjectType() { 91 | return targetInterface; 92 | } 93 | 94 | 95 | // 96 | @Autowired 97 | public void setDataSource(DataSource dataSource) { 98 | this.dataSource = dataSource; 99 | } 100 | 101 | @Autowired(required = false) 102 | public void setJdbc(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { 103 | this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; 104 | } 105 | 106 | @Autowired(required = false) 107 | public void setAdditionalColumnMapperFactories(List additionalColumnMapperFactories) { 108 | this.additionalColumnMapperFactories = additionalColumnMapperFactories; 109 | } 110 | 111 | @Autowired(required = false) 112 | public void setAdditionalParameterConverters(List additionalParameterConverters) { 113 | this.additionalParameterConverters = additionalParameterConverters; 114 | } 115 | // 116 | 117 | static class QueryTypeAndQuery { 118 | final QueryType type; 119 | final String query; 120 | final Class rowMapperClass; 121 | 122 | QueryTypeAndQuery(QueryType type, String query, Class rowMapperClass) { 123 | this.type = type; 124 | this.query = query; 125 | this.rowMapperClass = rowMapperClass; 126 | } 127 | } 128 | 129 | 130 | private QueryTypeAndQuery extractQueryAnnotation(Class clazz, Method method) { 131 | 132 | Query q = method.getAnnotation(Query.class); 133 | QueriesOverride qs = method.getAnnotation(QueriesOverride.class); 134 | 135 | // only one @Query annotation, thus we return the value without checking the database 136 | if (qs == null) { 137 | return new QueryTypeAndQuery(q.type(), q.value(), q.mapper()); 138 | } 139 | 140 | for (QueryOverride query : qs.value()) { 141 | if (query.db().equals(activeDB)) { 142 | return new QueryTypeAndQuery(q.type(), query.value(), query.mapper()); 143 | } 144 | } 145 | 146 | return new QueryTypeAndQuery(q.type(), q.value(), q.mapper()); 147 | } 148 | 149 | //from https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/ 150 | private static final Constructor LOOKUP_CONSTRUCTOR; 151 | private static final Method PRIVATE_LOOKUP_IN = ReflectionUtils.findMethod(MethodHandles.class, "privateLookupIn", Class.class, MethodHandles.Lookup.class); 152 | 153 | static { 154 | try { 155 | if(PRIVATE_LOOKUP_IN == null) { 156 | LOOKUP_CONSTRUCTOR = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); 157 | if (!LOOKUP_CONSTRUCTOR.isAccessible()) { //TODO: is deprecated 158 | LOOKUP_CONSTRUCTOR.setAccessible(true); 159 | } 160 | } else { 161 | LOOKUP_CONSTRUCTOR = null; 162 | } 163 | } catch (NoSuchMethodException | SecurityException e) { 164 | throw new IllegalStateException(e); 165 | } 166 | } 167 | 168 | 169 | @SuppressWarnings("unchecked") 170 | private T from(final Class clazz) { 171 | 172 | SortedSet columnMapperFactories = new TreeSet<>(Comparator.comparingInt(ColumnMapperFactory::order).thenComparing(Objects::hashCode)); 173 | columnMapperFactories.addAll(getDefaultFactories()); 174 | if (additionalColumnMapperFactories != null) { 175 | columnMapperFactories.addAll(additionalColumnMapperFactories); 176 | } 177 | 178 | SortedSet parameterConverters = new TreeSet<>(Comparator.comparingInt(ParameterConverter::order).thenComparing(Objects::hashCode)); 179 | parameterConverters.addAll(getDefaultParameterConverters()); 180 | if (additionalParameterConverters != null) { 181 | parameterConverters.addAll(additionalParameterConverters); 182 | } 183 | 184 | NamedParameterJdbcTemplate jdbc = namedParameterJdbcTemplate == null ? new NamedParameterJdbcTemplate(dataSource) : namedParameterJdbcTemplate; 185 | 186 | return (T) Proxy.newProxyInstance(clazz.getClassLoader(), 187 | new Class[] { clazz }, (proxy, method, args) -> { 188 | boolean hasAnnotation = method.getAnnotation(Query.class) != null; 189 | if(hasAnnotation) { 190 | QueryTypeAndQuery qs = extractQueryAnnotation(clazz, method); 191 | return qs.type.apply(qs, jdbc, method, args, columnMapperFactories, parameterConverters); 192 | } else if(method.getReturnType().equals(NamedParameterJdbcTemplate.class) && args == null) { 193 | return jdbc; 194 | } else if(method.isDefault()) { 195 | final Class declaringClass = method.getDeclaringClass(); 196 | final MethodHandle handle; 197 | if(PRIVATE_LOOKUP_IN != null) { 198 | MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 199 | handle = MethodHandles.lookup().findSpecial(declaringClass, method.getName(), methodType, declaringClass); 200 | } else { 201 | handle = LOOKUP_CONSTRUCTOR.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE).unreflectSpecial(method, declaringClass); 202 | } 203 | return handle.bindTo(proxy).invokeWithArguments(args); 204 | } else if (method.getDeclaringClass().equals(Object.class)) { 205 | String name = method.getName(); 206 | switch (name) { 207 | case "equals": return proxy == args[0]; 208 | case "hashCode": return System.identityHashCode(proxy); 209 | case "toString": return proxy.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(proxy)); 210 | default: throw new IllegalStateException(String.valueOf(method)); 211 | } 212 | } else { 213 | throw new IllegalArgumentException(String.format("missing @Query annotation for method %s in interface %s", method.getName(), clazz.getSimpleName())); 214 | } 215 | } 216 | ); 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/QueryOverride.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | import org.springframework.jdbc.core.RowMapper; 24 | 25 | @Retention(RetentionPolicy.RUNTIME) 26 | @Target(ElementType.METHOD) 27 | public @interface QueryOverride { 28 | String value(); 29 | 30 | String db(); 31 | 32 | @SuppressWarnings("rawtypes") 33 | Class mapper() default ConstructorAnnotationRowMapper.class; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/QueryRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @Target(ElementType.TYPE) 25 | public @interface QueryRepository { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/QueryType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | import java.lang.reflect.ParameterizedType; 22 | import java.util.*; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | 25 | import org.springframework.dao.IncorrectResultSizeDataAccessException; 26 | import org.springframework.jdbc.core.RowMapper; 27 | import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource; 28 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 29 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 30 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 31 | import org.springframework.jdbc.support.GeneratedKeyHolder; 32 | import org.springframework.jdbc.support.KeyHolder; 33 | import org.springframework.util.NumberUtils; 34 | import org.springframework.util.StringUtils; 35 | 36 | import ch.digitalfondue.npjt.QueryFactory.QueryTypeAndQuery; 37 | import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; 38 | import ch.digitalfondue.npjt.mapper.ParameterConverter; 39 | 40 | /** 41 | * Query Type: 42 | * 43 | *
    44 | *
  • TEMPLATE : we receive the string defined in @Query/@QueryOverride 45 | * annotation. 46 | *
  • EXECUTE : the query will be executed. If it's a select, the result will 47 | * be mapped with a ConstructorAnnotationRowMapper if it has the correct form. 48 | *
49 | * 50 | */ 51 | public enum QueryType { 52 | 53 | /** 54 | * Receive the string defined in @Query/@QueryOverride annotation. 55 | */ 56 | TEMPLATE { 57 | @Override 58 | String apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, 59 | Method method, Object[] args, 60 | SortedSet columnMapperFactories, SortedSet parameterConverters) { 61 | return queryTypeAndQuery.query; 62 | } 63 | }, 64 | 65 | /** 66 | */ 67 | EXECUTE { 68 | 69 | /** 70 | * Keep a mapping between a given class and a possible RowMapper. 71 | * 72 | * If the Class has the correct form, a ConstructorAnnotationRowMapper 73 | * will be built and the boolean set to true in the pair. If the class 74 | * has not the correct form, the boolean will be false and the class 75 | * will be used as it is in the jdbc template. 76 | */ 77 | private final Map, HasRowmapper> cachedClassToMapper = new ConcurrentHashMap<>(); 78 | 79 | @Override 80 | Object apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, 81 | Method method, Object[] args, 82 | SortedSet columnMapperFactories, SortedSet parameterConverters) { 83 | JdbcAction action = actionFromContext(method, queryTypeAndQuery); 84 | SqlParameterSource parameters = extractParameters(method, args, parameterConverters, jdbc); 85 | switch (action) { 86 | case QUERY: 87 | return doQuery(queryTypeAndQuery.query, queryTypeAndQuery.rowMapperClass, jdbc, method, parameters, columnMapperFactories); 88 | case UPDATE: 89 | return jdbc.update(queryTypeAndQuery.query, parameters); 90 | case INSERT_W_AUTO_GENERATED_KEY: 91 | return executeUpdateAndKeepKeys(queryTypeAndQuery.query, method, jdbc, parameters); 92 | default: 93 | throw new IllegalArgumentException("unknown value for action: " + action); 94 | } 95 | } 96 | 97 | 98 | @SuppressWarnings("unchecked") 99 | private Object doQuery(String template, Class rowMapper, 100 | NamedParameterJdbcTemplate jdbc, Method method, 101 | SqlParameterSource parameters, SortedSet columnMapperFactories) { 102 | boolean isReturnOptional = isReturnOptional(method); 103 | if (method.getReturnType().isAssignableFrom(List.class) || isReturnOptional) { 104 | Class c = extractGenericMethod(method); 105 | 106 | HasRowmapper r = getRowMapper(c, rowMapper, columnMapperFactories); 107 | 108 | List res = handleList(template, jdbc, parameters, columnMapperFactories, c, r, method); 109 | if(isReturnOptional) { 110 | return buildOptional(res); 111 | } else { 112 | return res; 113 | } 114 | } else { 115 | Class c = (Class) method.getReturnType(); 116 | HasRowmapper r = getRowMapper(c, rowMapper, columnMapperFactories); 117 | return handleSingleObject(template, jdbc, parameters, columnMapperFactories, c, r, method); 118 | } 119 | } 120 | 121 | @SuppressWarnings("unchecked") 122 | private HasRowmapper getRowMapper(Class c, Class rowMapper, SortedSet columnMapperFactories) { 123 | 124 | if(rowMapper != ConstructorAnnotationRowMapper.class) { 125 | try { 126 | return new HasRowmapper(true, (RowMapper) rowMapper.getConstructor().newInstance()); 127 | } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 128 | throw new IllegalArgumentException("Was not able to create a new instance of " + rowMapper + ". It require a 0 args constructor.", e); 129 | } 130 | } else if (!cachedClassToMapper.containsKey(c)) { 131 | cachedClassToMapper.put(c, handleClass(c, columnMapperFactories)); 132 | } 133 | return cachedClassToMapper.get(c); 134 | } 135 | }, 136 | /** 137 | * Specialized EXECUTE, will bypass the heuristic to determine if a query is a insert/update/delete or a select, will always treat the query as a select. 138 | */ 139 | SELECT { 140 | @Override 141 | Object apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, Method method, Object[] args, SortedSet columnMapperFactories, SortedSet parameterConverters) { 142 | return EXECUTE.apply(queryTypeAndQuery, jdbc, method, args, columnMapperFactories, parameterConverters); 143 | } 144 | }, 145 | /** 146 | * Specialized EXECUTE, will bypass the heuristic to determine if a query is a insert/update/delete or a select, will always treat the query as a insert/update/delete. 147 | */ 148 | MODIFYING { 149 | @Override 150 | Object apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, Method method, Object[] args, SortedSet columnMapperFactories, SortedSet parameterConverters) { 151 | return EXECUTE.apply(queryTypeAndQuery, jdbc, method, args, columnMapperFactories, parameterConverters); 152 | } 153 | }, 154 | /** 155 | * Specialized EXECUTE, will bypass the heuristic to determine if a query is a insert/update/delete or a select, will always treat the query as a select. 156 | */ 157 | MODIFYING_WITH_RETURN { 158 | @Override 159 | Object apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, Method method, Object[] args, SortedSet columnMapperFactories, SortedSet parameterConverters) { 160 | return EXECUTE.apply(queryTypeAndQuery, jdbc, method, args, columnMapperFactories, parameterConverters); 161 | } 162 | }; 163 | 164 | abstract Object apply(QueryTypeAndQuery queryTypeAndQuery, NamedParameterJdbcTemplate jdbc, 165 | Method method, Object[] args, 166 | SortedSet columnMapperFactories, SortedSet parameterConverters); 167 | 168 | private static Object handleSingleObject(String template, 169 | NamedParameterJdbcTemplate jdbc, SqlParameterSource parameters, 170 | SortedSet columnMapperFactories, 171 | Class c, HasRowmapper r, Method method) { 172 | if (r.present) { 173 | return jdbc.queryForObject(template, parameters, r.rowMapper); 174 | } else { 175 | RowMapper rowMapper = matchToOutput(columnMapperFactories, c, method.getAnnotations()); 176 | if(rowMapper != null) { 177 | return jdbc.queryForObject(template, parameters, rowMapper); 178 | } else { 179 | return jdbc.queryForObject(template, parameters, c); 180 | } 181 | 182 | } 183 | } 184 | 185 | @SuppressWarnings("unchecked") 186 | private static Class extractGenericMethod(Method method) { 187 | return (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; 188 | } 189 | 190 | protected Object buildOptional(List res) { 191 | if (res.size() > 1) { 192 | throw new IncorrectResultSizeDataAccessException(1, res.size()); 193 | } 194 | 195 | if(res.isEmpty()) { 196 | return Optional.empty(); 197 | } else { 198 | return Optional.ofNullable(res.iterator().next()); 199 | } 200 | } 201 | 202 | 203 | private static boolean isReturnOptional(Method method) { 204 | return method.getReturnType().isAssignableFrom(Optional.class); 205 | } 206 | 207 | private static RowMapper matchToOutput(SortedSet columnMapperFactories, Class o, Annotation[] annotations) { 208 | 209 | for(ColumnMapperFactory mapper : columnMapperFactories) { 210 | if(mapper.accept(o, annotations)) { 211 | return mapper.getSingleColumnRowMapper(o); 212 | } 213 | } 214 | return null; 215 | } 216 | 217 | private static JdbcAction actionFromContext(Method method, QueryTypeAndQuery queryTypeAndQuery) { 218 | 219 | if (method.getReturnType().isAssignableFrom(AffectedRowCountAndKey.class)) { 220 | return JdbcAction.INSERT_W_AUTO_GENERATED_KEY; 221 | } else if (queryTypeAndQuery.type == SELECT || queryTypeAndQuery.type == MODIFYING_WITH_RETURN) { 222 | return JdbcAction.QUERY; 223 | } else if (queryTypeAndQuery.type == MODIFYING) { 224 | return JdbcAction.UPDATE; 225 | } else { 226 | return actionFromTemplate(queryTypeAndQuery.query); 227 | } 228 | } 229 | 230 | private static JdbcAction actionFromTemplate(String template) { 231 | String tmpl = StringUtils.deleteAny(template.toLowerCase(Locale.ENGLISH), "() ").trim(); 232 | return tmpl.indexOf("select") == 0 ? JdbcAction.QUERY : JdbcAction.UPDATE; 233 | } 234 | 235 | private enum JdbcAction { 236 | QUERY, UPDATE, INSERT_W_AUTO_GENERATED_KEY 237 | } 238 | 239 | private static class HasRowmapper { 240 | private final boolean present; 241 | private final RowMapper rowMapper; 242 | 243 | HasRowmapper(boolean present, RowMapper rowMapper) { 244 | this.present = present; 245 | this.rowMapper = rowMapper; 246 | } 247 | } 248 | 249 | 250 | 251 | private static HasRowmapper handleClass(Class c, SortedSet columnMapperFactories) { 252 | if (ConstructorAnnotationRowMapper.hasConstructorInTheCorrectForm(c)) { 253 | return new HasRowmapper(true, new ConstructorAnnotationRowMapper<>(c, columnMapperFactories)); 254 | } else { 255 | return new HasRowmapper(false, null); 256 | } 257 | } 258 | 259 | private static SqlParameterSource extractParameters(Method m, Object[] args, SortedSet parameterConverters, NamedParameterJdbcTemplate jdbc) { 260 | 261 | Annotation[][] parameterAnnotations = m.getParameterAnnotations(); 262 | if (parameterAnnotations == null || parameterAnnotations.length == 0) { 263 | return new EmptySqlParameterSource(); 264 | } 265 | 266 | MapSqlParameterSource ps = new MapSqlParameterSource(); 267 | Class[] parameterTypes = m.getParameterTypes(); 268 | for (int i = 0; i < args.length; i++) { 269 | String name = parameterName(parameterAnnotations[i]); 270 | if (name != null) { 271 | Object arg = args[i]; 272 | Class parameterType = parameterTypes[i]; 273 | 274 | boolean hasAccepted = false; 275 | for (ParameterConverter parameterConverter : parameterConverters) { 276 | if (parameterConverter.accept(parameterType, parameterAnnotations[i])) { 277 | hasAccepted = true; 278 | if (parameterConverter instanceof ParameterConverter.AdvancedParameterConverter) { 279 | ((ParameterConverter.AdvancedParameterConverter) parameterConverter).processParameter(new ParameterConverter.ProcessParameterContext(jdbc, name, arg, parameterType, parameterAnnotations[i], ps)); 280 | } else { 281 | parameterConverter.processParameter(name, arg, parameterType, ps); 282 | } 283 | 284 | break; 285 | } 286 | } 287 | 288 | if (!hasAccepted) { 289 | throw new IllegalStateException("Was not able to find a ParameterConverter able to process object: " + arg + " with class " + parameterType); 290 | } 291 | } 292 | } 293 | 294 | return ps; 295 | } 296 | 297 | private static String parameterName(Annotation[] annotation) { 298 | 299 | if (annotation == null) { 300 | return null; 301 | } 302 | 303 | for (Annotation a : annotation) { 304 | if (a instanceof Bind) { 305 | return ((Bind) a).value(); 306 | } 307 | } 308 | return null; 309 | } 310 | 311 | 312 | @SuppressWarnings("unchecked") 313 | private static AffectedRowCountAndKey executeUpdateAndKeepKeys( 314 | String template, Method method, 315 | NamedParameterJdbcTemplate jdbc, SqlParameterSource parameters) { 316 | 317 | Class keyClass = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; 318 | 319 | KeyHolder keyHolder = new GeneratedKeyHolder(); 320 | 321 | int result = jdbc.update(template, parameters, keyHolder); 322 | Map keys = keyHolder.getKeys(); 323 | Object key; 324 | if (keys.size() > 1) { 325 | AutoGeneratedKey spec = Objects.requireNonNull(withType(method.getDeclaredAnnotations(), AutoGeneratedKey.class), "more than one key for query " + template + ": annotation @AutoGeneratedKey required"); 326 | key = Objects.requireNonNull(keys.get(spec.value()), "the key with name " + spec.value() + " has returned null for query " + template + ": required a non null key"); 327 | } else if (Number.class.isAssignableFrom(keyClass)) { 328 | Class c = (Class) keyClass; 329 | return new AffectedRowCountAndKey<>(result, (T) NumberUtils.convertNumberToTargetClass(keyHolder.getKey(), c)); 330 | } else { 331 | key = keys.values().iterator().next(); 332 | } 333 | return new AffectedRowCountAndKey<>(result, keyClass.cast(key)); 334 | } 335 | 336 | private static T withType(Annotation[] annotations, Class c) { 337 | if(annotations == null) { 338 | return null; 339 | } 340 | 341 | for(Annotation a : annotations) { 342 | if(a.annotationType() == c) { 343 | return (T) a; 344 | } 345 | } 346 | return null; 347 | } 348 | 349 | private static List handleList(String template, 350 | NamedParameterJdbcTemplate jdbc, SqlParameterSource parameters, 351 | SortedSet columnMapperFactories, 352 | Class c, HasRowmapper r, Method method) { 353 | if (r.present) { 354 | return jdbc.query(template, parameters, r.rowMapper); 355 | } else { 356 | RowMapper rowMapper = matchToOutput(columnMapperFactories, c, method.getAnnotations()); 357 | if(rowMapper != null) { 358 | return jdbc.query(template, parameters, rowMapper); 359 | } else { 360 | return jdbc.queryForList(template, parameters, c); 361 | } 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/RepositoriesDefinitionRegistrar.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import org.apache.commons.logging.LogFactory; 19 | import org.springframework.beans.BeansException; 20 | import org.springframework.beans.factory.BeanFactory; 21 | import org.springframework.beans.factory.BeanFactoryAware; 22 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 23 | import org.springframework.beans.factory.config.BeanDefinition; 24 | import org.springframework.beans.factory.config.BeanExpressionContext; 25 | import org.springframework.beans.factory.config.BeanExpressionResolver; 26 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 27 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 28 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 29 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 30 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 31 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 32 | import org.springframework.core.log.LogAccessor; 33 | import org.springframework.core.type.AnnotationMetadata; 34 | import org.springframework.core.type.filter.AnnotationTypeFilter; 35 | 36 | import java.util.Map; 37 | import java.util.Set; 38 | 39 | public class RepositoriesDefinitionRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { 40 | 41 | private BeanExpressionResolver resolver; 42 | private BeanExpressionContext expressionContext; 43 | 44 | private final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); 45 | 46 | @Override 47 | public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 48 | Map annotationAttributes = annotationMetadata.getAnnotationAttributes(EnableNpjt.class.getCanonicalName()); 49 | String[] basePackages = (String[]) annotationAttributes.get("basePackages"); 50 | String activeDb = (String) annotationAttributes.get("activeDB"); 51 | Class queryFactoryClass = (Class) annotationAttributes.get("queryFactory"); 52 | 53 | if (this.resolver != null) { 54 | activeDb = (String) this.resolver.evaluate(activeDb, expressionContext); 55 | } 56 | 57 | logger.info("ActiveDb is " + activeDb); 58 | 59 | if (basePackages != null) { 60 | CustomClasspathScanner scanner = new CustomClasspathScanner(); 61 | for (String packageToScan : basePackages) { 62 | Set candidates = scanner.findCandidateComponents(packageToScan); 63 | handleCandidates(candidates, beanDefinitionRegistry, activeDb, queryFactoryClass); 64 | } 65 | } 66 | } 67 | 68 | private void handleCandidates(Set candidates, BeanDefinitionRegistry beanDefinitionRegistry, 69 | String activeDB, Class queryFactoryClass) { 70 | try { 71 | for (BeanDefinition beanDefinition : candidates) { 72 | Class c = Class.forName(beanDefinition.getBeanClassName()); 73 | AbstractBeanDefinition abd = BeanDefinitionBuilder.rootBeanDefinition(queryFactoryClass) 74 | .addConstructorArgValue(c) 75 | .addConstructorArgValue(activeDB) 76 | .getBeanDefinition(); 77 | beanDefinitionRegistry.registerBeanDefinition(beanDefinition.getBeanClassName(), abd); 78 | } 79 | } catch (ClassNotFoundException cnf) { 80 | throw new IllegalStateException("Error while loading class", cnf); 81 | } 82 | } 83 | 84 | @Override 85 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 86 | if (beanFactory instanceof ConfigurableListableBeanFactory) { 87 | this.resolver = ((ConfigurableListableBeanFactory) beanFactory).getBeanExpressionResolver(); 88 | this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null); 89 | } 90 | } 91 | 92 | 93 | private static class CustomClasspathScanner extends ClassPathScanningCandidateComponentProvider { 94 | 95 | public CustomClasspathScanner() { 96 | super(false); 97 | addIncludeFilter(new AnnotationTypeFilter(QueryRepository.class, false)); 98 | } 99 | 100 | @Override 101 | protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 102 | return beanDefinition.getMetadata().isInterface(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/ColumnMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | 21 | public abstract class ColumnMapper { 22 | 23 | protected final String name; 24 | protected final Class paramType; 25 | 26 | public ColumnMapper(String name, Class paramType) { 27 | this.name = name; 28 | this.paramType = paramType; 29 | } 30 | 31 | public abstract Object getObject(ResultSet rs) throws SQLException; 32 | } -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/ColumnMapperFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import org.springframework.jdbc.core.RowMapper; 19 | 20 | import java.lang.annotation.Annotation; 21 | 22 | public interface ColumnMapperFactory { 23 | ColumnMapper build(String name, Class paramType); 24 | int order(); 25 | boolean accept(Class paramType, Annotation[] annotations); 26 | RowMapper getSingleColumnRowMapper(Class clzz); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/DefaultMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | 22 | import org.springframework.jdbc.core.RowMapper; 23 | import org.springframework.jdbc.core.SingleColumnRowMapper; 24 | import org.springframework.jdbc.core.StatementCreatorUtils; 25 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 26 | import org.springframework.jdbc.support.JdbcUtils; 27 | 28 | public class DefaultMapper extends ColumnMapper { 29 | 30 | private static final int ORDER = Integer.MAX_VALUE; 31 | 32 | public DefaultMapper(String name, Class paramType) { 33 | super(name, paramType); 34 | } 35 | 36 | public Object getObject(ResultSet rs) throws SQLException { 37 | int columnIdx = rs.findColumn(name); 38 | return JdbcUtils.getResultSetValue(rs, columnIdx, paramType); 39 | } 40 | 41 | public static class Converter implements ParameterConverter { 42 | 43 | @Override 44 | public boolean accept(Class parameterType, Annotation[] annotations) { 45 | return true; 46 | } 47 | 48 | @Override 49 | public void processParameter(String parameterName, Object arg, 50 | Class parameterType, MapSqlParameterSource ps) { 51 | ps.addValue(parameterName, arg, StatementCreatorUtils.javaTypeToSqlParameterType(parameterType)); 52 | } 53 | 54 | @Override 55 | public int order() { 56 | return ORDER; 57 | } 58 | 59 | } 60 | 61 | 62 | public static class Factory implements ColumnMapperFactory { 63 | 64 | @Override 65 | public ColumnMapper build(String name, Class paramType) { 66 | return new DefaultMapper(name, paramType); 67 | } 68 | 69 | @Override 70 | public int order() { 71 | return ORDER; 72 | } 73 | 74 | @Override 75 | public boolean accept(Class paramType, Annotation[] annotations) { 76 | return true; 77 | } 78 | 79 | @Override 80 | public RowMapper getSingleColumnRowMapper(Class clzz) { 81 | return new SingleColumnRowMapper<>(clzz); 82 | } 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/EnumMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | 22 | import org.springframework.jdbc.core.RowMapper; 23 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 24 | 25 | public class EnumMapper extends ColumnMapper { 26 | 27 | private static final int ORDER = Integer.MAX_VALUE - 1; 28 | 29 | 30 | public EnumMapper(String name, Class paramType) { 31 | super(name, paramType); 32 | } 33 | 34 | 35 | @Override 36 | public Object getObject(ResultSet rs) throws SQLException { 37 | String res = rs.getString(name); 38 | return toEnum(res, paramType); 39 | } 40 | 41 | @SuppressWarnings({ "unchecked", "rawtypes" }) 42 | private static Object toEnum(String res, Class paramType) { 43 | Class enumType = (Class>) paramType; 44 | return res == null ? null : Enum.valueOf(enumType, res.trim()); 45 | } 46 | 47 | public static class Converter implements ParameterConverter { 48 | 49 | @Override 50 | public boolean accept(Class parameterType, Annotation[] annotations) { 51 | return parameterType.isEnum(); 52 | } 53 | 54 | @Override 55 | public void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps) { 56 | ps.addValue(parameterName, arg == null ? null : ((Enum)arg).name()); 57 | } 58 | 59 | @Override 60 | public int order() { 61 | return ORDER; 62 | } 63 | 64 | } 65 | 66 | 67 | public static class Factory implements ColumnMapperFactory { 68 | 69 | @Override 70 | public ColumnMapper build(String name, Class paramType) { 71 | return new EnumMapper(name, paramType); 72 | } 73 | 74 | @Override 75 | public int order() { 76 | return ORDER; 77 | } 78 | 79 | @Override 80 | public boolean accept(Class paramType, Annotation[] annotations) { 81 | return paramType.isEnum(); 82 | } 83 | 84 | @Override 85 | public RowMapper getSingleColumnRowMapper(final Class clazz) { 86 | return (rs, rowNum) -> toEnum(rs.getString(1), clazz); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/InstantMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | import java.sql.Timestamp; 22 | import java.sql.Types; 23 | import java.time.Instant; 24 | 25 | import org.springframework.jdbc.core.RowMapper; 26 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 27 | 28 | public class InstantMapper extends ColumnMapper { 29 | 30 | private static final int ORDER = Integer.MAX_VALUE - 5; 31 | 32 | public InstantMapper(String name, Class paramType) { 33 | super(name, paramType); 34 | } 35 | 36 | @Override 37 | public Object getObject(ResultSet rs) throws SQLException { 38 | return toInstant(rs.getTimestamp(name)); 39 | } 40 | 41 | private static Instant toInstant(Timestamp ts) { 42 | return ts != null ? ts.toInstant() : null; 43 | } 44 | 45 | public static class Converter implements ParameterConverter { 46 | 47 | @Override 48 | public boolean accept(Class parameterType, Annotation[] annotations) { 49 | return Instant.class.equals(parameterType); 50 | } 51 | 52 | @Override 53 | public void processParameter(String parameterName, Object arg, 54 | Class parameterType, MapSqlParameterSource ps) { 55 | ps.addValue(parameterName, arg != null ? Timestamp.from((Instant) arg) : null, Types.TIMESTAMP); 56 | } 57 | 58 | @Override 59 | public int order() { 60 | return ORDER; 61 | } 62 | } 63 | 64 | public static class Factory implements ColumnMapperFactory { 65 | 66 | @Override 67 | public ColumnMapper build(String name, Class paramType) { 68 | return new InstantMapper(name, paramType); 69 | } 70 | 71 | @Override 72 | public int order() { 73 | return ORDER; 74 | } 75 | 76 | @Override 77 | public boolean accept(Class paramType, Annotation[] annotations) { 78 | return Instant.class.equals(paramType); 79 | } 80 | 81 | @Override 82 | public RowMapper getSingleColumnRowMapper(Class clzz) { 83 | return (rs, rowNum) -> toInstant(rs.getTimestamp(1)); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/LocalDateMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.Date; 20 | import java.sql.ResultSet; 21 | import java.sql.SQLException; 22 | import java.sql.Types; 23 | import java.time.LocalDate; 24 | 25 | import org.springframework.jdbc.core.RowMapper; 26 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 27 | 28 | public class LocalDateMapper extends ColumnMapper { 29 | 30 | private static final int ORDER = Integer.MAX_VALUE - 3; 31 | 32 | public LocalDateMapper(String name, Class paramType) { 33 | super(name, paramType); 34 | } 35 | 36 | @Override 37 | public Object getObject(ResultSet rs) throws SQLException { 38 | return toLocalDate(rs.getDate(name)); 39 | } 40 | 41 | private static LocalDate toLocalDate(Date d) { 42 | return d != null ? d.toLocalDate() : null; 43 | } 44 | 45 | public static class Converter implements ParameterConverter { 46 | 47 | @Override 48 | public boolean accept(Class parameterType, Annotation[] annotations) { 49 | return LocalDate.class.equals(parameterType); 50 | } 51 | 52 | @Override 53 | public void processParameter(String parameterName, Object arg, 54 | Class parameterType, MapSqlParameterSource ps) { 55 | ps.addValue(parameterName, arg != null ? Date.valueOf((LocalDate) arg) : null, Types.DATE); 56 | } 57 | 58 | @Override 59 | public int order() { 60 | return ORDER; 61 | } 62 | } 63 | 64 | 65 | public static class Factory implements ColumnMapperFactory { 66 | 67 | @Override 68 | public ColumnMapper build(String name, Class paramType) { 69 | return new LocalDateMapper(name, paramType); 70 | } 71 | 72 | @Override 73 | public int order() { 74 | return ORDER; 75 | } 76 | 77 | @Override 78 | public boolean accept(Class paramType, Annotation[] annotations) { 79 | return LocalDate.class.equals(paramType); 80 | } 81 | 82 | @Override 83 | public RowMapper getSingleColumnRowMapper(Class clzz) { 84 | return (rs, rowNum) -> toLocalDate(rs.getDate(1)); 85 | } 86 | 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/LocalDateTimeMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | import java.sql.Timestamp; 22 | import java.sql.Types; 23 | import java.time.LocalDateTime; 24 | 25 | import org.springframework.jdbc.core.RowMapper; 26 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 27 | 28 | public class LocalDateTimeMapper extends ColumnMapper { 29 | 30 | private static final int ORDER = Integer.MAX_VALUE - 4; 31 | 32 | public LocalDateTimeMapper(String name, Class paramType) { 33 | super(name, paramType); 34 | } 35 | 36 | @Override 37 | public Object getObject(ResultSet rs) throws SQLException { 38 | return toLocalDateTime(rs.getTimestamp(name)); 39 | } 40 | 41 | private static LocalDateTime toLocalDateTime(Timestamp t) { 42 | return t != null ? t.toLocalDateTime() : null; 43 | } 44 | 45 | public static class Converter implements ParameterConverter { 46 | 47 | @Override 48 | public boolean accept(Class parameterType, Annotation[] annotations) { 49 | return LocalDateTime.class.equals(parameterType); 50 | } 51 | 52 | @Override 53 | public void processParameter(String parameterName, Object arg, 54 | Class parameterType, MapSqlParameterSource ps) { 55 | ps.addValue(parameterName, arg != null ? Timestamp.valueOf(((LocalDateTime) arg)) : null, Types.TIMESTAMP); 56 | } 57 | 58 | @Override 59 | public int order() { 60 | return ORDER; 61 | } 62 | 63 | } 64 | 65 | public static class Factory implements ColumnMapperFactory { 66 | 67 | @Override 68 | public ColumnMapper build(String name, Class paramType) { 69 | return new LocalDateTimeMapper(name, paramType); 70 | } 71 | 72 | @Override 73 | public int order() { 74 | return ORDER; 75 | } 76 | 77 | @Override 78 | public boolean accept(Class paramType, Annotation[] annotations) { 79 | return LocalDateTime.class.equals(paramType); 80 | } 81 | 82 | @Override 83 | public RowMapper getSingleColumnRowMapper(Class clzz) { 84 | return (rs, rowNum) -> toLocalDateTime(rs.getTimestamp(1)); 85 | } 86 | 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/ParameterConverter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 19 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 20 | import org.springframework.jdbc.datasource.DataSourceUtils; 21 | 22 | import java.lang.annotation.Annotation; 23 | import java.sql.Connection; 24 | 25 | public interface ParameterConverter { 26 | 27 | boolean accept(Class parameterType, Annotation[] annotations); 28 | 29 | 30 | interface AdvancedParameterConverter extends ParameterConverter { 31 | void processParameter(ProcessParameterContext processParameterContext); 32 | 33 | @Override 34 | default void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps) { 35 | throw new IllegalStateException("should not be executed"); 36 | } 37 | } 38 | 39 | /** 40 | * 41 | * 42 | * @param parameterName 43 | * @param arg 44 | * @param parameterType 45 | * @param ps 46 | */ 47 | void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps); 48 | 49 | int order(); 50 | 51 | class ProcessParameterContext { 52 | private final NamedParameterJdbcTemplate jdbc; 53 | private final String parameterName; 54 | private final Class parameterType; 55 | private final Annotation[] parameterAnnotations; 56 | private final Object arg; 57 | private final MapSqlParameterSource ps; 58 | 59 | public ProcessParameterContext(NamedParameterJdbcTemplate jdbc, String parameterName, Object arg, Class parameterType, Annotation[] parameterAnnotations, MapSqlParameterSource ps) { 60 | this.jdbc = jdbc; 61 | this.parameterName = parameterName; 62 | this.arg = arg; 63 | this.parameterType = parameterType; 64 | this.parameterAnnotations = parameterAnnotations; 65 | this.ps = ps; 66 | } 67 | 68 | public NamedParameterJdbcTemplate getJdbc() { 69 | return jdbc; 70 | } 71 | 72 | public Connection getConnection() { 73 | return DataSourceUtils.getConnection(jdbc.getJdbcTemplate().getDataSource()); 74 | } 75 | 76 | public Class getParameterType() { 77 | return parameterType; 78 | } 79 | 80 | public Annotation[] getParameterAnnotations() { 81 | return parameterAnnotations; 82 | } 83 | 84 | public Object getArg() { 85 | return arg; 86 | } 87 | 88 | public String getParameterName() { 89 | return parameterName; 90 | } 91 | 92 | public MapSqlParameterSource getParameterSource() { 93 | return ps; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/ch/digitalfondue/npjt/mapper/ZonedDateTimeMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | import java.sql.Timestamp; 22 | import java.sql.Types; 23 | import java.time.ZoneId; 24 | import java.time.ZonedDateTime; 25 | import java.util.Calendar; 26 | import java.util.TimeZone; 27 | 28 | import org.springframework.jdbc.core.RowMapper; 29 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 30 | 31 | public class ZonedDateTimeMapper extends ColumnMapper { 32 | 33 | private static final int ORDER = Integer.MAX_VALUE -2; 34 | 35 | private static final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC"); 36 | private static final ZoneId UTC_Z_ID = ZoneId.of("UTC"); 37 | 38 | public ZonedDateTimeMapper(String name, Class paramType) { 39 | super(name, paramType); 40 | } 41 | 42 | public Object getObject(ResultSet rs) throws SQLException { 43 | Timestamp timestamp = rs.getTimestamp(name, Calendar.getInstance(UTC_TZ)); 44 | return toZonedDateTime(timestamp); 45 | } 46 | 47 | private static Object toZonedDateTime(Timestamp timestamp) { 48 | if (timestamp == null) { 49 | return null; 50 | } 51 | return ZonedDateTime.ofInstant(timestamp.toInstant(), UTC_Z_ID); 52 | } 53 | 54 | public static class Converter implements ParameterConverter { 55 | 56 | @Override 57 | public boolean accept(Class parameterType, Annotation[] annotations) { 58 | return ZonedDateTime.class.isAssignableFrom(parameterType); 59 | } 60 | 61 | @Override 62 | public void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps) { 63 | Calendar c = null; 64 | if(arg != null) { 65 | ZonedDateTime dateTime = ZonedDateTime.class.cast(arg); 66 | ZonedDateTime utc = dateTime.withZoneSameInstant(UTC_Z_ID); 67 | c = Calendar.getInstance(); 68 | c.setTimeZone(UTC_TZ); 69 | c.setTimeInMillis(utc.toInstant().toEpochMilli()); 70 | } 71 | ps.addValue(parameterName, c, Types.TIMESTAMP); 72 | } 73 | 74 | @Override 75 | public int order() { 76 | return ORDER; 77 | } 78 | 79 | } 80 | 81 | 82 | 83 | public static class Factory implements ColumnMapperFactory { 84 | 85 | @Override 86 | public ColumnMapper build(String name, Class paramType) { 87 | return new ZonedDateTimeMapper(name, paramType); 88 | } 89 | 90 | @Override 91 | public int order() { 92 | return ORDER; 93 | } 94 | 95 | @Override 96 | public boolean accept(Class paramType, Annotation[] annotations) { 97 | return ZonedDateTime.class.isAssignableFrom(paramType); 98 | } 99 | 100 | @Override 101 | public RowMapper getSingleColumnRowMapper(Class clzz) { 102 | return (rs, rowNum) -> { 103 | Timestamp timestamp = rs.getTimestamp(1, Calendar.getInstance(UTC_TZ)); 104 | return toZonedDateTime(timestamp); 105 | }; 106 | } 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/ConstructorAnnotationRowMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 | * This file is part of lavagna. 18 | * 19 | * lavagna is free software: you can redistribute it and/or modify 20 | * it under the terms of the GNU General Public License as published by 21 | * the Free Software Foundation, either version 3 of the License, or 22 | * (at your option) any later version. 23 | * 24 | * lavagna is distributed in the hope that it will be useful, 25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | * GNU General Public License for more details. 28 | * 29 | * You should have received a copy of the GNU General Public License 30 | * along with lavagna. If not, see . 31 | */ 32 | package ch.digitalfondue.npjt; 33 | 34 | import java.util.Collections; 35 | import java.util.List; 36 | 37 | import org.junit.Assert; 38 | import org.junit.Test; 39 | 40 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 41 | import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; 42 | import ch.digitalfondue.npjt.mapper.DefaultMapper.Factory; 43 | 44 | public class ConstructorAnnotationRowMapperTest { 45 | 46 | private static final List DEFAULT_COLUMN_MAPPER_FACTORY = Collections.singletonList(new Factory()); 47 | 48 | @Test 49 | public void testCorrectMapping() { 50 | new ConstructorAnnotationRowMapper<>(Mapping.class, DEFAULT_COLUMN_MAPPER_FACTORY); 51 | } 52 | 53 | @Test(expected = IllegalArgumentException.class) 54 | public void testsMoreThanOnePublicConstructor() { 55 | new ConstructorAnnotationRowMapper<>(MultiplePublicConstructor.class, DEFAULT_COLUMN_MAPPER_FACTORY); 56 | } 57 | 58 | @Test(expected = IllegalStateException.class) 59 | public void testMissingColumnAnnotation() { 60 | new ConstructorAnnotationRowMapper<>(MissingColumn.class, DEFAULT_COLUMN_MAPPER_FACTORY); 61 | } 62 | 63 | @Test(expected = IllegalStateException.class) 64 | public void testNoMatchingColumnMapperFactory() { 65 | new ConstructorAnnotationRowMapper<>(Mapping.class, Collections.emptyList()); 66 | } 67 | 68 | @Test 69 | public void testsMoreThanOnePublicConstructorForm() { 70 | Assert.assertFalse(ConstructorAnnotationRowMapper.hasConstructorInTheCorrectForm(MultiplePublicConstructor.class)); 71 | } 72 | 73 | @Test 74 | public void testMissingColumnAnnotationForm() { 75 | Assert.assertFalse(ConstructorAnnotationRowMapper.hasConstructorInTheCorrectForm(MissingColumn.class)); 76 | } 77 | 78 | @Test 79 | public void testZeroArgConstructorForm() { 80 | Assert.assertFalse(ConstructorAnnotationRowMapper.hasConstructorInTheCorrectForm(ZeroArgConstructor.class)); 81 | } 82 | 83 | 84 | public static class Mapping { 85 | public Mapping(@Column("COL_1") String a, @Column("COL_2") int b) { 86 | } 87 | } 88 | 89 | public static class ZeroArgConstructor { 90 | 91 | public ZeroArgConstructor() { 92 | } 93 | 94 | } 95 | 96 | public static class MultiplePublicConstructor { 97 | public MultiplePublicConstructor() { 98 | } 99 | 100 | public MultiplePublicConstructor(String s) { 101 | } 102 | } 103 | 104 | public static class MissingColumn { 105 | public MissingColumn(String a) { 106 | 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/QueryFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 | * This file is part of lavagna. 18 | * 19 | * lavagna is free software: you can redistribute it and/or modify 20 | * it under the terms of the GNU General Public License as published by 21 | * the Free Software Foundation, either version 3 of the License, or 22 | * (at your option) any later version. 23 | * 24 | * lavagna is distributed in the hope that it will be useful, 25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | * GNU General Public License for more details. 28 | * 29 | * You should have received a copy of the GNU General Public License 30 | * along with lavagna. If not, see . 31 | */ 32 | package ch.digitalfondue.npjt; 33 | 34 | import org.junit.Assert; 35 | import org.junit.Test; 36 | import org.junit.runner.RunWith; 37 | import org.mockito.Mock; 38 | import org.mockito.runners.MockitoJUnitRunner; 39 | 40 | import javax.sql.DataSource; 41 | 42 | @RunWith(MockitoJUnitRunner.class) 43 | public class QueryFactoryTest { 44 | 45 | @Mock 46 | DataSource dataSource; 47 | 48 | public interface QueryTest { 49 | @Query(type = QueryType.TEMPLATE, value = "SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = :columnId") 50 | String findById(); 51 | 52 | @Query(type = QueryType.TEMPLATE, value = "SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = :columnId") 53 | @QueriesOverride(@QueryOverride(db = "MYSQL", value = "SELECT * FROM LA_BOARD_COLUMN_FULL_MYSQL WHERE BOARD_COLUMN_ID = :columnId")) 54 | String overrideQuery(); 55 | } 56 | 57 | @Test 58 | public void testSimpleAnnotationQuery() { 59 | QueryFactory qf = new QueryFactory<>(QueryTest.class, "HSQLDB"); 60 | qf.setDataSource(dataSource); 61 | 62 | QueryTest qt = qf.getObject(); 63 | 64 | Assert.assertEquals("SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = :columnId", qt.findById()); 65 | } 66 | 67 | @Test 68 | public void testOverrideAnnotation() { 69 | QueryFactory qf = new QueryFactory<>(QueryTest.class, "HSQLDB"); 70 | qf.setDataSource(dataSource); 71 | QueryTest qt = qf.getObject(); 72 | Assert.assertEquals("SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = :columnId", qt.overrideQuery()); 73 | 74 | 75 | QueryFactory qfMysql = new QueryFactory<>(QueryTest.class, "MYSQL"); 76 | qfMysql.setDataSource(dataSource); 77 | QueryTest qtMysql = qfMysql.getObject(); 78 | Assert.assertEquals("SELECT * FROM LA_BOARD_COLUMN_FULL_MYSQL WHERE BOARD_COLUMN_ID = :columnId", qtMysql.overrideQuery()); 79 | } 80 | 81 | @Test 82 | public void testObjectCallOnProxiedInterface() { 83 | QueryFactory qf = new QueryFactory<>(QueryTest.class, "HSQLDB"); 84 | qf.setDataSource(dataSource); 85 | QueryTest qt = qf.getObject(); 86 | 87 | Assert.assertTrue(qt.toString().startsWith("com.sun.proxy.$Proxy")); 88 | qt.hashCode(); //<- should not fail 89 | Assert.assertTrue(qt.equals(qt)); 90 | Assert.assertFalse(qt.equals(qf)); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/QueryRepositoryScannerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import org.junit.Assert; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.test.context.ContextConfiguration; 23 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 24 | import org.springframework.transaction.annotation.Transactional; 25 | 26 | import ch.digitalfondue.npjt.query.QueryRepo; 27 | import ch.digitalfondue.npjt.query.deeper.QueryRepo2; 28 | 29 | @Transactional 30 | @RunWith(SpringJUnit4ClassRunner.class) 31 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 32 | public class QueryRepositoryScannerTest { 33 | 34 | @Autowired 35 | QueryRepo queryRepo; 36 | 37 | @Autowired 38 | QueryRepo2 queryRepo2; 39 | 40 | @Test 41 | public void checkInjection() { 42 | Assert.assertNotNull(queryRepo); 43 | Assert.assertNotNull(queryRepo2); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/QueryScannerConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | @EnableNpjt(basePackages = {"ch.digitalfondue.npjt.query", "ch.digitalfondue.npjt.columnmapper"}) 19 | public class QueryScannerConfiguration { 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/TestJdbcConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt; 17 | 18 | import java.util.Properties; 19 | 20 | import javax.sql.DataSource; 21 | 22 | import org.hsqldb.jdbc.JDBCDataSourceFactory; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 25 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 26 | import org.springframework.transaction.PlatformTransactionManager; 27 | 28 | public class TestJdbcConfiguration { 29 | 30 | @Bean 31 | public DataSource getDataSource() throws Exception { 32 | Properties prop = new Properties(); 33 | prop.put("url", "jdbc:hsqldb:mem:extra"); 34 | prop.put("user", "sa"); 35 | prop.put("password", ""); 36 | return JDBCDataSourceFactory.createDataSource(prop); 37 | } 38 | 39 | /*@Bean 40 | DataSource getPostgresqlDataSource() { 41 | DriverManagerDataSource ds = new DriverManagerDataSource(); 42 | ds.setDriverClassName("org.postgresql.Driver"); 43 | ds.setUrl("jdbc:postgresql://localhost:5432/alfio"); 44 | ds.setUsername("postgres"); 45 | ds.setPassword("password"); 46 | return ds; 47 | }*/ 48 | 49 | @Bean 50 | public PlatformTransactionManager getPlatfomrTransactionManager(DataSource dataSource) { 51 | return new DataSourceTransactionManager(dataSource); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/mapper/DefaultMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import static org.mockito.Mockito.when; 19 | 20 | import java.sql.ResultSet; 21 | import java.sql.SQLException; 22 | 23 | import org.junit.Assert; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mock; 27 | import org.mockito.runners.MockitoJUnitRunner; 28 | 29 | import ch.digitalfondue.npjt.mapper.DefaultMapper; 30 | 31 | @RunWith(MockitoJUnitRunner.class) 32 | public class DefaultMapperTest { 33 | 34 | @Mock 35 | ResultSet resultSet; 36 | 37 | @Test 38 | public void testNull() throws SQLException { 39 | DefaultMapper m = new DefaultMapper("PARAM", String.class); 40 | Assert.assertNull(m.getObject(resultSet)); 41 | } 42 | 43 | @Test 44 | public void testString() throws SQLException { 45 | DefaultMapper m = new DefaultMapper("PARAM", String.class); 46 | when(resultSet.findColumn("PARAM")).thenReturn(1); 47 | when(resultSet.getString(1)).thenReturn("MY_VALUE"); 48 | Assert.assertEquals("MY_VALUE", m.getObject(resultSet)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/mapper/EnumMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import static org.mockito.Mockito.when; 19 | 20 | import java.sql.ResultSet; 21 | import java.sql.SQLException; 22 | 23 | import org.junit.Assert; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mock; 27 | import org.mockito.runners.MockitoJUnitRunner; 28 | 29 | import ch.digitalfondue.npjt.mapper.EnumMapper; 30 | 31 | @RunWith(MockitoJUnitRunner.class) 32 | public class EnumMapperTest { 33 | 34 | public enum MyEnum { 35 | BLA, TEST; 36 | } 37 | 38 | @Mock 39 | ResultSet resultSet; 40 | 41 | @Test 42 | public void testNull() throws SQLException { 43 | EnumMapper m = new EnumMapper("PARAM", MyEnum.class); 44 | Assert.assertNull(m.getObject(resultSet)); 45 | } 46 | 47 | @Test 48 | public void testValue() throws SQLException { 49 | EnumMapper m = new EnumMapper("PARAM", MyEnum.class); 50 | when(resultSet.getString("PARAM")).thenReturn("BLA"); 51 | Assert.assertEquals(MyEnum.BLA, m.getObject(resultSet)); 52 | 53 | when(resultSet.getString("PARAM")).thenReturn("TEST"); 54 | Assert.assertEquals(MyEnum.TEST, m.getObject(resultSet)); 55 | } 56 | 57 | @Test(expected = IllegalArgumentException.class) 58 | public void testWrongValue() throws SQLException { 59 | EnumMapper m = new EnumMapper("PARAM", MyEnum.class); 60 | when(resultSet.getString("PARAM")).thenReturn("NOT_IN_ENUM"); 61 | m.getObject(resultSet); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/mapper/ZonedDateTimeMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.mapper; 17 | 18 | import static org.mockito.Mockito.*; 19 | 20 | import java.sql.ResultSet; 21 | import java.sql.SQLException; 22 | import java.sql.Timestamp; 23 | import java.time.ZonedDateTime; 24 | import java.util.Calendar; 25 | 26 | import org.junit.Assert; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.mockito.Mock; 30 | import org.mockito.runners.MockitoJUnitRunner; 31 | 32 | import ch.digitalfondue.npjt.mapper.ZonedDateTimeMapper; 33 | 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class ZonedDateTimeMapperTest { 36 | 37 | @Mock 38 | ResultSet resultSet; 39 | 40 | @Test 41 | public void testNull() throws SQLException { 42 | ZonedDateTimeMapper m = new ZonedDateTimeMapper("PARAM", ZonedDateTime.class); 43 | Assert.assertNull(m.getObject(resultSet)); 44 | } 45 | 46 | @Test 47 | public void testFromTimestampToZonedDateTime() throws SQLException { 48 | ZonedDateTimeMapper m = new ZonedDateTimeMapper("PARAM", ZonedDateTime.class); 49 | 50 | final int time = 42; 51 | 52 | when(resultSet.getTimestamp(eq("PARAM"), any(Calendar.class))).thenReturn(new Timestamp(time)); 53 | 54 | ZonedDateTime res = (ZonedDateTime) m.getObject(resultSet); 55 | Assert.assertEquals(time, res.toInstant().toEpochMilli()); 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/AutogeneratedKeyQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import ch.digitalfondue.npjt.*; 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.test.context.ContextConfiguration; 24 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 25 | import org.springframework.transaction.annotation.Transactional; 26 | 27 | @Transactional 28 | @RunWith(SpringJUnit4ClassRunner.class) 29 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 30 | public class AutogeneratedKeyQueriesTest { 31 | 32 | @Autowired 33 | AutogeneratedKeyQueries aq; 34 | 35 | @Test 36 | public void autogeneratedKeyQueries() { 37 | 38 | aq.createTable(); 39 | 40 | AffectedRowCountAndKey res = aq.insert("test"); 41 | AffectedRowCountAndKey res2 = aq.insert("test2"); 42 | AffectedRowCountAndKey res3 = aq.insert("test3"); 43 | 44 | Assert.assertEquals(aq.findKeyByValue("test"), res.getKey()); 45 | Assert.assertEquals(1, res.getAffectedRowCount()); 46 | 47 | Assert.assertEquals(aq.findKeyByValue("test2"), res2.getKey()); 48 | Assert.assertEquals(1, res2.getAffectedRowCount()); 49 | 50 | Assert.assertEquals(aq.findKeyByValue("test3"), res3.getKey()); 51 | Assert.assertEquals(1, res3.getAffectedRowCount()); 52 | } 53 | 54 | @QueryRepository 55 | public interface AutogeneratedKeyQueries { 56 | @Query("CREATE TABLE LA_AUTO (ID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL, VALUE CLOB NOT NULL)") 57 | void createTable(); 58 | 59 | @Query("INSERT INTO LA_AUTO(VALUE) VALUES (:value)") 60 | AffectedRowCountAndKey insert(@Bind("value") String value); 61 | 62 | @Query("SELECT ID FROM LA_AUTO WHERE VALUE = :value") 63 | Integer findKeyByValue(@Bind("value") String value); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/BooleanQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import ch.digitalfondue.npjt.*; 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.test.context.ContextConfiguration; 24 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 25 | import org.springframework.transaction.annotation.Transactional; 26 | 27 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 28 | 29 | @Transactional 30 | @RunWith(SpringJUnit4ClassRunner.class) 31 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 32 | public class BooleanQueriesTest { 33 | 34 | @Autowired 35 | BoolQueries bq; 36 | 37 | @Test 38 | public void simpleQueriesTest() { 39 | 40 | bq.createTable(); 41 | 42 | bq.insertValue("KEY", true, true, true); 43 | 44 | BoolConf bc = bq.findByKey("KEY"); 45 | 46 | Assert.assertTrue(bc.confBool); 47 | Assert.assertTrue(bc.confStr); 48 | Assert.assertTrue(bc.confInt); 49 | 50 | bq.insertValue("KEY2", false, true, false); 51 | BoolConf bc2 = bq.findByKey("KEY2"); 52 | 53 | Assert.assertFalse(bc2.confBool); 54 | Assert.assertTrue(bc2.confStr); 55 | Assert.assertFalse(bc2.confInt); 56 | 57 | Assert.assertTrue(bq.findConfBoolByKey("KEY")); 58 | Assert.assertFalse(bq.findConfBoolByKey("KEY2")); 59 | 60 | } 61 | 62 | public static class BoolConf { 63 | 64 | final String key; 65 | final boolean confBool; 66 | final Boolean confStr; 67 | final boolean confInt; 68 | 69 | public BoolConf(@Column("CONF_KEY") String key, 70 | @Column("CONF_BOOL") boolean confBool, @Column("CONF_STR") Boolean confStr, 71 | @Column("CONF_INT") boolean confInt) { 72 | this.key = key; 73 | this.confBool = confBool; 74 | this.confStr = confStr; 75 | this.confInt = confInt; 76 | } 77 | 78 | } 79 | 80 | @QueryRepository 81 | public interface BoolQueries { 82 | @Query("CREATE TABLE LA_CONF_BOOL (CONF_KEY VARCHAR(64) PRIMARY KEY NOT NULL, CONF_BOOL BOOLEAN NOT NULL, CONF_STR VARCHAR(255) NOT NULL, CONF_INT INTEGER NOT NULL)") 83 | void createTable(); 84 | 85 | @Query("INSERT INTO LA_CONF_BOOL(CONF_KEY, CONF_BOOL, CONF_STR, CONF_INT) VALUES(:key, :confBool, :confStr, :confInt)") 86 | int insertValue(@Bind("key") String key, 87 | @Bind("confBool") Boolean confBool, 88 | @Bind("confStr") Boolean confStr, 89 | @Bind("confInt") Boolean confInt); 90 | 91 | @Query("SELECT * FROM LA_CONF_BOOL WHERE CONF_KEY = :key") 92 | BoolConf findByKey(@Bind("key") String key); 93 | 94 | @Query("SELECT CONF_BOOL FROM LA_CONF_BOOL WHERE CONF_KEY = :key") 95 | Boolean findConfBoolByKey(@Bind("key") String key); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/CustomJSONQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import ch.digitalfondue.npjt.*; 19 | import ch.digitalfondue.npjt.mapper.ColumnMapper; 20 | import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; 21 | import ch.digitalfondue.npjt.mapper.ParameterConverter; 22 | import com.google.gson.Gson; 23 | import com.google.gson.GsonBuilder; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.jdbc.core.RowMapper; 30 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 31 | import org.springframework.test.context.ContextConfiguration; 32 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 33 | import org.springframework.transaction.annotation.Transactional; 34 | 35 | import java.lang.annotation.*; 36 | import java.sql.ResultSet; 37 | import java.sql.SQLException; 38 | import java.util.Arrays; 39 | import java.util.Collections; 40 | import java.util.List; 41 | import java.util.Map; 42 | 43 | @Transactional 44 | @RunWith(SpringJUnit4ClassRunner.class) 45 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, 46 | CustomJSONQueriesTest.ColumnMapperAndParametersConfiguration.class, 47 | QueryScannerConfiguration.class}) 48 | public class CustomJSONQueriesTest { 49 | 50 | @Autowired 51 | JsonQueries jq; 52 | 53 | private static Gson JSON = new GsonBuilder().create(); 54 | 55 | @Retention(RetentionPolicy.RUNTIME) 56 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 57 | public @interface AsJson { 58 | } 59 | 60 | private static class JsonColumnMapper extends ColumnMapper { 61 | 62 | JsonColumnMapper(String name, Class paramType) { 63 | super(name, paramType); 64 | } 65 | 66 | @Override 67 | public Object getObject(ResultSet rs) throws SQLException { 68 | return JSON.fromJson(rs.getString(name), paramType); 69 | } 70 | } 71 | 72 | private static boolean containAsJsonAnnotation(Annotation[] annotations) { 73 | if(annotations == null) { 74 | return false; 75 | } 76 | for(Annotation annotation : annotations) { 77 | if(annotation.annotationType() == AsJson.class) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | private static class JsonColumnMapperFactory implements ColumnMapperFactory { 85 | 86 | @Override 87 | public ColumnMapper build(String name, Class paramType) { 88 | return new JsonColumnMapper(name, paramType); 89 | } 90 | 91 | @Override 92 | public int order() { 93 | return 0; 94 | } 95 | 96 | @Override 97 | public boolean accept(Class paramType, Annotation[] annotations) { 98 | return containAsJsonAnnotation(annotations); 99 | } 100 | 101 | @Override 102 | public RowMapper getSingleColumnRowMapper(final Class clzz) { 103 | return (resultSet, rowNum) -> JSON.fromJson(resultSet.getString(1), clzz); 104 | } 105 | } 106 | 107 | private static class JsonParameterConverter implements ParameterConverter.AdvancedParameterConverter { 108 | 109 | @Override 110 | public boolean accept(Class parameterType, Annotation[] annotations) { 111 | return containAsJsonAnnotation(annotations); 112 | } 113 | 114 | @Override 115 | public void processParameter(ProcessParameterContext ctx) { 116 | ctx.getParameterSource().addValue(ctx.getParameterName(), JSON.toJson(ctx.getArg(), ctx.getParameterType())); 117 | } 118 | 119 | 120 | @Override 121 | public int order() { 122 | return 0; 123 | } 124 | } 125 | 126 | //test class to check we don't override converters with the same order 127 | public static class DummyColumnMapperFactory implements ColumnMapperFactory { 128 | 129 | @Override 130 | public ColumnMapper build(String name, Class paramType) { 131 | return null; 132 | } 133 | 134 | @Override 135 | public int order() { 136 | return 0; 137 | } 138 | 139 | @Override 140 | public boolean accept(Class paramType, Annotation[] annotations) { 141 | return false; 142 | } 143 | 144 | @Override 145 | public RowMapper getSingleColumnRowMapper(Class clzz) { 146 | return null; 147 | } 148 | } 149 | 150 | //test class to check we don't override converters with the same order 151 | public static class DummyParameterConverter implements ParameterConverter { 152 | 153 | @Override 154 | public boolean accept(Class parameterType, Annotation[] annotations) { 155 | return false; 156 | } 157 | 158 | @Override 159 | public void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps) { 160 | ps.addValue(parameterName, null); 161 | } 162 | 163 | @Override 164 | public int order() { 165 | return 0; 166 | } 167 | } 168 | 169 | public static class ColumnMapperAndParametersConfiguration { 170 | 171 | @Bean 172 | List getColumnMapper() { 173 | return Arrays.asList(new DummyColumnMapperFactory(), new JsonColumnMapperFactory()); 174 | } 175 | 176 | @Bean 177 | List getParameterConverter() { 178 | return Arrays.asList(new DummyParameterConverter(), new JsonParameterConverter()); 179 | } 180 | } 181 | 182 | @Test 183 | public void simpleQueriesTest() { 184 | 185 | 186 | jq.createTable(); 187 | 188 | Map map = Collections.singletonMap("MY_KEY", "MY_VALUE"); 189 | jq.insertValue("TEST", map); 190 | 191 | JsonConf conf = jq.findByKey("TEST"); 192 | Assert.assertTrue(conf.conf.equals(map)); 193 | 194 | Assert.assertTrue(jq.findConfBoolByKey("TEST").equals(map)); 195 | 196 | } 197 | 198 | public static class JsonConf { 199 | 200 | final String key; 201 | final Map conf; 202 | 203 | public JsonConf(@ConstructorAnnotationRowMapper.Column("CONF_KEY") String key, 204 | @ConstructorAnnotationRowMapper.Column("CONF_JSON") @AsJson Map conf) { 205 | this.key = key; 206 | this.conf = conf; 207 | } 208 | 209 | } 210 | 211 | @QueryRepository 212 | public interface JsonQueries { 213 | @Query("CREATE TABLE LA_CONF_JSON (CONF_KEY VARCHAR(64) PRIMARY KEY NOT NULL, CONF_JSON CLOB NOT NULL)") 214 | void createTable(); 215 | 216 | @Query("INSERT INTO LA_CONF_JSON(CONF_KEY, CONF_JSON) VALUES(:key, :confJson)") 217 | int insertValue(@Bind("key") String key, @Bind("confJson") @AsJson Map conf); 218 | 219 | @Query("SELECT * FROM LA_CONF_JSON WHERE CONF_KEY = :key") 220 | JsonConf findByKey(@Bind("key") String key); 221 | 222 | @Query("SELECT CONF_JSON FROM LA_CONF_JSON WHERE CONF_KEY = :key") 223 | @AsJson 224 | Map findConfBoolByKey(@Bind("key") String key); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/DateTimeQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import java.time.Instant; 19 | import java.time.LocalDate; 20 | import java.time.LocalDateTime; 21 | import java.time.LocalTime; 22 | import java.time.ZoneId; 23 | import java.time.ZonedDateTime; 24 | import java.time.temporal.ChronoUnit; 25 | 26 | import ch.digitalfondue.npjt.*; 27 | import org.junit.Assert; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.test.context.ContextConfiguration; 32 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 33 | import org.springframework.transaction.annotation.Transactional; 34 | 35 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 36 | 37 | @Transactional 38 | @RunWith(SpringJUnit4ClassRunner.class) 39 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 40 | public class DateTimeQueriesTest { 41 | 42 | @Autowired 43 | DateQueries dq; 44 | 45 | @Test 46 | public void dateQueriesTest() { 47 | 48 | dq.createTable(); 49 | 50 | ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); 51 | 52 | dq.insertValue("KEY", now); 53 | check(dq, "KEY", now); 54 | 55 | dq.insertValue("KEY2", now.toLocalDate()); 56 | check(dq, "KEY2", now.toLocalDate()); 57 | 58 | dq.insertValue("KEY3", now.toLocalDateTime()); 59 | check(dq, "KEY3", now); 60 | 61 | Instant iNow = Instant.now(); 62 | dq.insertValue("KEY4", iNow); 63 | Assert.assertEquals(iNow, dq.findInstantByKey("KEY4")); 64 | Assert.assertEquals(iNow, dq.findConfInstantByKey("KEY4").value); 65 | 66 | } 67 | 68 | private static LocalDateTime truncateToSec(LocalDateTime localDateTime) { 69 | return localDateTime.truncatedTo(ChronoUnit.SECONDS); 70 | } 71 | 72 | private static ZonedDateTime truncateToSec(ZonedDateTime zonedDateTime) { 73 | return zonedDateTime.truncatedTo(ChronoUnit.SECONDS); 74 | } 75 | 76 | private void check(DateQueries dq, String key, LocalDate now) { 77 | Assert.assertEquals(now, dq.findByKey(key).valueLocalDate); 78 | Assert.assertEquals(truncateToSec(LocalDateTime.of(now, LocalTime.MIDNIGHT)), truncateToSec(dq.findByKey(key).valueLocalDateTime)); 79 | } 80 | 81 | private void check(DateQueries dq, String key, ZonedDateTime now) { 82 | Assert.assertEquals(truncateToSec(now), truncateToSec(dq.findByKey(key).value)); 83 | Assert.assertEquals(truncateToSec(now), truncateToSec(dq.findDateByKey(key))); 84 | Assert.assertEquals(now.toLocalDate(), dq.findByKey(key).valueLocalDate); 85 | Assert.assertEquals(truncateToSec(now.toLocalDateTime()), truncateToSec(dq.findByKey(key).valueLocalDateTime)); 86 | } 87 | 88 | public static class Conf { 89 | final String key; 90 | final ZonedDateTime value; 91 | final LocalDate valueLocalDate; 92 | final LocalDateTime valueLocalDateTime; 93 | 94 | public Conf(@Column("CONF_KEY") String key, 95 | @Column("CONF_VALUE") ZonedDateTime value, 96 | @Column("CONF_VALUE") LocalDate valueLocalDate, 97 | @Column("CONF_VALUE") LocalDateTime valueLocalDateTime) { 98 | this.key = key; 99 | this.value = value; 100 | this.valueLocalDate = valueLocalDate; 101 | this.valueLocalDateTime = valueLocalDateTime; 102 | } 103 | } 104 | 105 | public static class ConfInstant { 106 | final String key; 107 | final Instant value; 108 | 109 | public ConfInstant(@Column("CONF_KEY") String key, 110 | @Column("CONF_VALUE") Instant value) { 111 | this.key = key; 112 | this.value = value; 113 | } 114 | } 115 | 116 | @QueryRepository 117 | public interface DateQueries { 118 | 119 | @Query("CREATE TABLE LA_CONF_DATE (CONF_KEY VARCHAR(64) PRIMARY KEY NOT NULL, CONF_VALUE TIMESTAMP NOT NULL)") 120 | void createTable(); 121 | 122 | @Query("INSERT INTO LA_CONF_DATE(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 123 | int insertValue(@Bind("key") String key, @Bind("value") ZonedDateTime date); 124 | 125 | @Query("INSERT INTO LA_CONF_DATE(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 126 | int insertValue(@Bind("key") String key, @Bind("value") LocalDate date); 127 | 128 | @Query("INSERT INTO LA_CONF_DATE(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 129 | int insertValue(@Bind("key") String key, @Bind("value") LocalDateTime date); 130 | 131 | @Query("INSERT INTO LA_CONF_DATE(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 132 | int insertValue(@Bind("key") String key, @Bind("value") Instant date); 133 | 134 | @Query("SELECT * FROM LA_CONF_DATE WHERE CONF_KEY = :key") 135 | Conf findByKey(@Bind("key") String key); 136 | 137 | @Query("SELECT CONF_VALUE FROM LA_CONF_DATE WHERE CONF_KEY = :key") 138 | Instant findInstantByKey(@Bind("key") String key); 139 | 140 | @Query("SELECT * FROM LA_CONF_DATE WHERE CONF_KEY = :key") 141 | ConfInstant findConfInstantByKey(@Bind("key") String key); 142 | 143 | @Query("SELECT CONF_VALUE FROM LA_CONF_DATE WHERE CONF_KEY = :key") 144 | ZonedDateTime findDateByKey(@Bind("key") String key); 145 | 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/EnumQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import java.util.List; 19 | 20 | import ch.digitalfondue.npjt.*; 21 | import org.junit.Assert; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.test.context.ContextConfiguration; 26 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 27 | import org.springframework.transaction.annotation.Transactional; 28 | 29 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 30 | 31 | @Transactional 32 | @RunWith(SpringJUnit4ClassRunner.class) 33 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 34 | public class EnumQueriesTest { 35 | 36 | @Autowired 37 | EnumQueries eq; 38 | 39 | @Test 40 | public void enumQueriesTest() { 41 | eq.createTable(); 42 | 43 | eq.insert(null); 44 | 45 | eq.insert(TestEnum.TEST); 46 | eq.insert(TestEnum.TEST2); 47 | 48 | Assert.assertEquals(null, eq.findByNull()); 49 | Assert.assertEquals(TestEnum.TEST, eq.findByKey(TestEnum.TEST)); 50 | Assert.assertEquals(TestEnum.TEST2, eq.findByKey(TestEnum.TEST2)); 51 | 52 | Assert.assertEquals(TestEnum.TEST, eq.findContainerByKey(TestEnum.TEST).key); 53 | Assert.assertEquals(null, eq.findContainerByKeyNull().key); 54 | 55 | List all = eq.findAll(); 56 | Assert.assertTrue(all.contains(TestEnum.TEST)); 57 | Assert.assertTrue(all.contains(TestEnum.TEST2)); 58 | Assert.assertTrue(all.contains(null)); 59 | } 60 | 61 | public enum TestEnum { 62 | TEST, TEST2; 63 | } 64 | 65 | public static class EnumContainer { 66 | final TestEnum key; 67 | 68 | public EnumContainer(@Column("CONF_KEY") TestEnum key) { 69 | this.key = key; 70 | } 71 | } 72 | 73 | @QueryRepository 74 | public interface EnumQueries { 75 | 76 | @Query("CREATE TABLE LA_CONF_ENUM (CONF_KEY VARCHAR(64))") 77 | void createTable(); 78 | 79 | @Query("INSERT INTO LA_CONF_ENUM (CONF_KEY) VALUES (:key)") 80 | int insert(@Bind("key") TestEnum test); 81 | 82 | //most useless method ever :D 83 | @Query("SELECT CONF_KEY FROM LA_CONF_ENUM WHERE CONF_KEY = :key") 84 | TestEnum findByKey(@Bind("key") TestEnum test); 85 | 86 | @Query("SELECT CONF_KEY FROM LA_CONF_ENUM WHERE CONF_KEY is null") 87 | TestEnum findByNull(); 88 | 89 | @Query("SELECT CONF_KEY FROM LA_CONF_ENUM WHERE CONF_KEY = :key") 90 | EnumContainer findContainerByKey(@Bind("key") TestEnum test); 91 | 92 | @Query("SELECT CONF_KEY FROM LA_CONF_ENUM WHERE CONF_KEY is null") 93 | EnumContainer findContainerByKeyNull(); 94 | 95 | @Query("SELECT CONF_KEY FROM LA_CONF_ENUM") 96 | List findAll(); 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/QueryRepo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import ch.digitalfondue.npjt.QueryRepository; 19 | 20 | @QueryRepository 21 | public interface QueryRepo { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/SimpleQueriesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query; 17 | 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | import ch.digitalfondue.npjt.*; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.jdbc.core.RowMapper; 29 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 30 | import org.springframework.test.context.ContextConfiguration; 31 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 32 | import org.springframework.transaction.annotation.Transactional; 33 | 34 | import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; 35 | 36 | import javax.sql.DataSource; 37 | 38 | @Transactional 39 | @RunWith(SpringJUnit4ClassRunner.class) 40 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, QueryScannerConfiguration.class}) 41 | public class SimpleQueriesTest { 42 | 43 | @Autowired 44 | DataSource dataSource; 45 | 46 | @Autowired 47 | MySimpleQueries mq; 48 | 49 | /** 50 | * Simple DB interaction. 51 | */ 52 | @Test 53 | public void simpleQueriesTest() { 54 | 55 | mq.createTable(); 56 | 57 | Assert.assertTrue(mq.findAll().isEmpty()); 58 | Assert.assertEquals(1, mq.insertValue("MY_KEY", "MY_VALUE")); 59 | 60 | Conf conf = mq.findByKey("MY_KEY"); 61 | 62 | Assert.assertEquals("MY_KEY", conf.key); 63 | Assert.assertEquals("MY_VALUE", conf.value); 64 | 65 | Assert.assertFalse(mq.findAll().isEmpty()); 66 | 67 | Assert.assertEquals(1, mq.update("MY_KEY", "MY_VALUE_UPDATED")); 68 | 69 | Conf confUpd = mq.findByKey("MY_KEY"); 70 | Assert.assertEquals("MY_KEY", confUpd.key); 71 | Assert.assertEquals("MY_VALUE_UPDATED", confUpd.value); 72 | 73 | Assert.assertTrue(mq.findAllKeys().contains("MY_KEY")); 74 | 75 | Assert.assertEquals("MY_VALUE_UPDATED", mq.findValueForKey("MY_KEY")); 76 | 77 | Assert.assertEquals("MY_TEMPLATE", mq.template()); 78 | 79 | Assert.assertNotNull(mq.getNamedParameterJdbcTemplate()); 80 | 81 | Assert.assertEquals("MY_VALUE_UPDATED", mq.findOptionalValueForKey("MY_KEY").get()); 82 | 83 | Assert.assertFalse(mq.findOptionalValueForKey("MY_KEY_NOT").isPresent()); 84 | 85 | Assert.assertEquals("MY_VALUE_UPDATED", mq.findOptionalWrappedValueForKey("MY_KEY").get().value); 86 | 87 | Assert.assertFalse(mq.findOptionalWrappedValueForKey("MY_KEY_NOT").isPresent()); 88 | 89 | Assert.assertEquals("defaultMethod", mq.defaultMethod()); 90 | 91 | // 92 | MySimpleQueries mq2 = QueryFactory.from(MySimpleQueries.class, "HSQLDB", dataSource); 93 | 94 | Assert.assertEquals(mq.findByKey("MY_KEY"), mq2.findByKey("MY_KEY")); 95 | // 96 | } 97 | 98 | public static class Conf { 99 | final String key; 100 | final String value; 101 | 102 | public Conf(@Column("CONF_KEY") String key, 103 | @Column("CONF_VALUE") String value) { 104 | this.key = key; 105 | this.value = value; 106 | } 107 | 108 | @Override 109 | public boolean equals(Object obj) { 110 | if(obj != null && obj instanceof Conf) { 111 | Conf c2 = (Conf) obj; 112 | return key.equals(c2.key) && value.equals(c2.value); 113 | } 114 | return false; 115 | } 116 | } 117 | 118 | public static class MyCustomWrapper { 119 | final String value; 120 | 121 | public MyCustomWrapper(String value) { 122 | this.value = value; 123 | } 124 | } 125 | 126 | public static class MyCustomWrapperRowMapper implements RowMapper { 127 | 128 | @Override 129 | public MyCustomWrapper mapRow(ResultSet rs, int rowNum) throws SQLException { 130 | return new MyCustomWrapper(rs.getString(1)); 131 | } 132 | 133 | } 134 | 135 | @QueryRepository 136 | public interface MySimpleQueries { 137 | 138 | @Query("CREATE TABLE LA_CONF (CONF_KEY VARCHAR(64) PRIMARY KEY NOT NULL, CONF_VALUE CLOB NOT NULL)") 139 | void createTable(); 140 | 141 | @Query("INSERT INTO LA_CONF(CONF_KEY, CONF_VALUE) VALUES(:key, :value)") 142 | int insertValue(@Bind("key") String key, @Bind("value") String value); 143 | 144 | @Query("SELECT * FROM LA_CONF WHERE CONF_KEY = :key") 145 | Conf findByKey(@Bind("key") String key); 146 | 147 | @Query("SELECT * FROM LA_CONF") 148 | List findAll(); 149 | 150 | @Query("SELECT CONF_KEY FROM LA_CONF") 151 | List findAllKeys(); 152 | 153 | @Query("SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = :key") 154 | String findValueForKey(@Bind("key") String key); 155 | 156 | @Query("SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = :key") 157 | Optional findOptionalValueForKey(@Bind("key") String key); 158 | 159 | @Query(value = "SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = :key", mapper = MyCustomWrapperRowMapper.class) 160 | Optional findOptionalWrappedValueForKey(@Bind("key") String key); 161 | 162 | @Query("UPDATE LA_CONF SET CONF_VALUE = :value WHERE CONF_KEY = :key") 163 | int update(@Bind("key") String key, @Bind("value") String value); 164 | 165 | @Query(type = QueryType.TEMPLATE, value = "MY_TEMPLATE") 166 | String template(); 167 | 168 | NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(); 169 | 170 | default String defaultMethod() { 171 | return "defaultMethod"; 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/customfactory/CustomJSONQueriesWithCustomQueryFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query.customfactory; 17 | 18 | import ch.digitalfondue.npjt.*; 19 | import ch.digitalfondue.npjt.mapper.ColumnMapper; 20 | import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; 21 | import ch.digitalfondue.npjt.mapper.ParameterConverter; 22 | import com.google.gson.Gson; 23 | import com.google.gson.GsonBuilder; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.jdbc.core.RowMapper; 30 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 31 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 32 | import org.springframework.test.context.ContextConfiguration; 33 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 34 | import org.springframework.transaction.annotation.Transactional; 35 | 36 | import javax.sql.DataSource; 37 | import java.lang.annotation.*; 38 | import java.sql.ResultSet; 39 | import java.sql.SQLException; 40 | import java.util.*; 41 | 42 | @Transactional 43 | @RunWith(SpringJUnit4ClassRunner.class) 44 | @ContextConfiguration(classes = {TestJdbcConfiguration.class, CustomJSONQueriesWithCustomQueryFactoryTest.CustomQueryFactoryConf.class}) 45 | public class CustomJSONQueriesWithCustomQueryFactoryTest { 46 | 47 | @Autowired 48 | JsonQueries jq; 49 | 50 | private static Gson JSON = new GsonBuilder().create(); 51 | 52 | @Retention(RetentionPolicy.RUNTIME) 53 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 54 | public @interface AsJson { 55 | } 56 | 57 | private static class JsonColumnMapper extends ColumnMapper { 58 | 59 | JsonColumnMapper(String name, Class paramType) { 60 | super(name, paramType); 61 | } 62 | 63 | @Override 64 | public Object getObject(ResultSet rs) throws SQLException { 65 | return JSON.fromJson(rs.getString(name), paramType); 66 | } 67 | } 68 | 69 | private static boolean containAsJsonAnnotation(Annotation[] annotations) { 70 | if(annotations == null) { 71 | return false; 72 | } 73 | for(Annotation annotation : annotations) { 74 | if(annotation.annotationType() == AsJson.class) { 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | private static class JsonColumnMapperFactory implements ColumnMapperFactory { 82 | 83 | @Override 84 | public ColumnMapper build(String name, Class paramType) { 85 | return new JsonColumnMapper(name, paramType); 86 | } 87 | 88 | @Override 89 | public int order() { 90 | return 0; 91 | } 92 | 93 | @Override 94 | public boolean accept(Class paramType, Annotation[] annotations) { 95 | return containAsJsonAnnotation(annotations); 96 | } 97 | 98 | @Override 99 | public RowMapper getSingleColumnRowMapper(final Class clzz) { 100 | return (resultSet, rowNum) -> JSON.fromJson(resultSet.getString(1), clzz); 101 | } 102 | } 103 | 104 | private static class JsonParameterConverter implements ParameterConverter { 105 | 106 | @Override 107 | public boolean accept(Class parameterType, Annotation[] annotations) { 108 | return containAsJsonAnnotation(annotations); 109 | } 110 | 111 | @Override 112 | public void processParameter(String parameterName, Object arg, Class parameterType, MapSqlParameterSource ps) { 113 | ps.addValue(parameterName, JSON.toJson(arg, parameterType)); 114 | } 115 | 116 | @Override 117 | public int order() { 118 | return 0; 119 | } 120 | } 121 | 122 | @EnableNpjt(queryFactory = CustomQueryFactory.class, basePackages = {"ch.digitalfondue.npjt.query.customfactory"}) 123 | public static class CustomQueryFactoryConf { 124 | 125 | @Bean 126 | public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(DataSource dataSource) { 127 | return new NamedParameterJdbcTemplate(dataSource); 128 | } 129 | } 130 | 131 | public static class CustomQueryFactory extends QueryFactory { 132 | 133 | public CustomQueryFactory(Class targetInterface, String activeDB) { 134 | super(targetInterface, activeDB); 135 | } 136 | 137 | @Override 138 | public List getDefaultFactories() { 139 | List a = super.getDefaultFactories(); 140 | a.add(new JsonColumnMapperFactory()); 141 | return a; 142 | } 143 | 144 | @Override 145 | public List getDefaultParameterConverters() { 146 | List a = super.getDefaultParameterConverters(); 147 | a.add(new JsonParameterConverter()); 148 | return a; 149 | } 150 | } 151 | 152 | @Test 153 | public void simpleQueriesTest() { 154 | 155 | 156 | jq.createTable(); 157 | 158 | Map map = Collections.singletonMap("MY_KEY", "MY_VALUE"); 159 | jq.insertValue("TEST", map); 160 | 161 | JsonConf conf = jq.findByKey("TEST"); 162 | Assert.assertTrue(conf.conf.equals(map)); 163 | 164 | Assert.assertTrue(jq.findConfBoolByKey("TEST").equals(map)); 165 | 166 | } 167 | 168 | public static class JsonConf { 169 | 170 | final String key; 171 | final Map conf; 172 | 173 | public JsonConf(@ConstructorAnnotationRowMapper.Column("CONF_KEY") String key, 174 | @ConstructorAnnotationRowMapper.Column("CONF_JSON") @AsJson Map conf) { 175 | this.key = key; 176 | this.conf = conf; 177 | } 178 | 179 | } 180 | 181 | @QueryRepository 182 | public interface JsonQueries { 183 | @Query("CREATE TABLE LA_CONF_JSON2 (CONF_KEY VARCHAR(64) PRIMARY KEY NOT NULL, CONF_JSON CLOB NOT NULL)") 184 | void createTable(); 185 | 186 | @Query("INSERT INTO LA_CONF_JSON2(CONF_KEY, CONF_JSON) VALUES(:key, :confJson)") 187 | int insertValue(@Bind("key") String key, @Bind("confJson") @AsJson Map conf); 188 | 189 | @Query("SELECT * FROM LA_CONF_JSON2 WHERE CONF_KEY = :key") 190 | JsonConf findByKey(@Bind("key") String key); 191 | 192 | @Query("SELECT CONF_JSON FROM LA_CONF_JSON2 WHERE CONF_KEY = :key") 193 | @AsJson 194 | Map findConfBoolByKey(@Bind("key") String key); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/test/java/ch/digitalfondue/npjt/query/deeper/QueryRepo2.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 digitalfondue (info@digitalfondue.ch) 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 | * http://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 ch.digitalfondue.npjt.query.deeper; 17 | 18 | import ch.digitalfondue.npjt.QueryRepository; 19 | 20 | @QueryRepository 21 | public interface QueryRepo2 { 22 | 23 | } 24 | --------------------------------------------------------------------------------