├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── generate-dependency-info.sh ├── generated-runtime-deps-list.txt ├── generated-runtime-deps-tree.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml ├── settings.gradle └── src ├── main └── java │ └── com │ └── github │ └── rwitzel │ └── couchrepository │ ├── api │ ├── CouchDbCrudRepository.java │ ├── CouchDbCrudRepositoryFactory.java │ ├── EntityInformation.java │ ├── ViewParams.java │ ├── ViewResult.java │ ├── ViewResultRow.java │ └── exceptions │ │ ├── BulkOperationError.java │ │ ├── BulkOperationException.java │ │ └── UnsupportedViewParameterException.java │ ├── ektorp │ ├── EktorpCouchViewConfigurer.java │ ├── EktorpCrudRepository.java │ └── EktorpEntityInformation.java │ ├── internal │ ├── AdapterUtils.java │ ├── QueryMethodHandler.java │ └── ViewParamsMerger.java │ ├── lightcouch │ ├── LightCouchCrudRepository.java │ └── LightCouchViewConfigurer.java │ └── support │ ├── DocumentLoader.java │ ├── GenericEntityInformation.java │ └── SimpleEntityInformation.java └── test ├── java └── com │ └── github │ └── rwitzel │ └── couchrepository │ ├── api │ ├── AbstractAutomaticImplementationTest.java │ ├── AbstractCouchdbCrudRepositoryTest.java │ ├── AbstractCrudRepositoryTest.java │ ├── AbstractCustomImplementationTest.java │ ├── AbstractExoticTest.java │ ├── ProductRepository.java │ ├── ProductRepositoryCustom.java │ ├── ProductRepositoryCustomImpl.java │ └── viewresult │ │ ├── ProductFacts.java │ │ └── ProductSummary.java │ ├── ektorp │ ├── EktorpCrudRepositoryTest.java │ └── EktorpTestConfiguration.java │ ├── internal │ └── ViewParamsMergerTest.java │ ├── lightcouch │ ├── LightCouchCrudRepositoryTest.java │ └── LightCouchTestConfiguration.java │ ├── model │ ├── BaseDocument.java │ ├── Comment.java │ ├── Exotic.java │ ├── ExoticEntityInformation.java │ ├── ExoticId.java │ ├── Manufacturer.java │ ├── Product.java │ └── ProductRating.java │ └── support │ └── DocumentLoaderTest.java └── resources ├── com └── github │ └── rwitzel │ └── couchrepository │ ├── Exotic.json │ ├── Manufacturer.json │ ├── Product.json │ ├── Product.yaml │ └── documents │ └── p1_allAttributesSet.js └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | 3 | .gradle 4 | /build 5 | 6 | # IDE project files 7 | .project 8 | .settings 9 | .classpath 10 | .idea 11 | *.iml 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | services: 6 | - couchdb 7 | 8 | before_install: 9 | - chmod +x gradlew 10 | 11 | after_success: 12 | - mvn clean test jacoco:report coveralls:report -Pcoverage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Some parts of this documentation require knowledge about CouchDB IDs, revisions, and views. 2 | 3 | [![Travis build status](https://travis-ci.org/rwitzel/CouchRepository.svg)](https://travis-ci.org/rwitzel/CouchRepository) 4 | [![Coveralls coverage status](https://img.shields.io/coveralls/rwitzel/CouchRepository.svg)](https://coveralls.io/r/rwitzel/CouchRepository) 5 | [![Apache 2](http://img.shields.io/badge/license-Apache%202-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.rwitzel/couchrepository-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.rwitzel/couchrepository-core/) 7 | 8 | 9 | When should I use CouchRepository? 10 | ================================== 11 | 12 | There are two main use cases. 13 | 14 | A. You choose CouchRepository when 15 | 16 | * you are going to use [Spring Data](http://projects.spring.io/spring-data/) for a new project and 17 | * you do not want to decide about the eventual database solution yet. 18 | 19 | In this case, you make your new application configurable so that one can easily switch between different persistence solutions (SQL, MongoDB, CouchDB). 20 | 21 | B. You choose CouchRepository when 22 | 23 | * you want to stick with the familiar Spring Data framework, and 24 | * you are going to work with an existing [CouchDB](http://couchdb.apache.org/) database, and 25 | * you need only basic CRUD operations and simple view queries in the first place. 26 | 27 | As soon as you want to use more sophisticated operations on the CouchDB, you will directly use the underlying Java CouchDB drivers, namely LightCouch or Ektorp. 28 | 29 | 30 | How do I use CouchRepository? 31 | ============================= 32 | 33 | You may want to checkout the source code and have a look at the tests to find working examples for both the Ektorp and LightCouch drivers. 34 | 35 | Prerequisites 36 | ------------- 37 | 38 | CouchRepository requires Java 8 or higher. 39 | 40 | Basics 41 | ------ 42 | 43 | Here is a short example that assumes you use Ektorp as CouchDB driver. The code for LightCouch is almost the same. 44 | 45 | First of all you add the needed dependencies to your project. 46 | The dependencies for Ektorp must be added explicitly because you can choose between Ektorp and LightCouch. 47 | 48 | compile 'com.github.rwitzel:couchrepository-core:1.0.0' 49 | compile 'org.ektorp:org.ektorp:1.4.2' 50 | compile 'org.ektorp:org.ektorp.spring:1.4.2' 51 | 52 | Have a look a the [CouchRepository Example](https://github.com/rwitzel/CouchRepository-example) to get a full list of the needed dependencies. 53 | 54 | Then you set up a `CouchDbConnector` as described by the Ektorp documentation. 55 | 56 | CouchDbConnector db = ... 57 | 58 | Then you create a repository for each type of entities. For an entity type like `Product` you write 59 | 60 | CouchDbCrudRepository productRepository = new EktorpCrudRepository(Product.class, db); 61 | 62 | The type `CouchDbCrudRepository` extends Spring Data's `CrudRepository` so that you are able to query CouchDB views immediately by using the `find` method. 63 | 64 | One thing is still missing. The repository requires a design document (by default named `_design/Product`) that provides a view named `by_id`. 65 | This view must provide ID and revision for each entity of the given type. The reduce function must be `_count`. The design document may look like this. 66 | 67 | { 68 | "_id" : "_design/Product", 69 | "language" : "javascript", 70 | "views" : { 71 | "by_id" : { 72 | "map" : "function(doc) { if(doc.type == 'Product') {emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", 73 | "reduce" : "_count" 74 | } 75 | } 76 | 77 | One way to load the design document into the database is `DocumentLoader` but you can use any other mean, of course. 78 | You can use the repository as soon the design document is available in the database. 79 | 80 | DocumentLoader loader = new DocumentLoader(new EktorpCrudRepository(Map.class, db)); 81 | loader.loadJson(getClass().getResourceAsStream("../Product.json")); // design document Product.json is taken from the classpath 82 | 83 | In case you prefer YAML documents, you can use the method `loadYaml` instead of `loadJson`. 84 | In YAML documents you don't have to care about the formatting of the contained Javascript functions. 85 | 86 | Finally, your Java-based Spring configuration might look like this. 87 | 88 | @Configuration 89 | public class CouchDbRepositoriesConfiguration { 90 | 91 | @Bean 92 | public StdCouchDbConnector connector() throws Exception { ... } 93 | 94 | @Bean 95 | public CouchDbCrudRepository productRepository(CouchDbConnector db) { ... } 96 | 97 | @Bean @Lazy(false) 98 | public String initializeDatabaseAndDesignDocuments(CouchDbConnector db) { ... } 99 | } 100 | 101 | Automatic implementation of finder methods 102 | ------------------------------------------ 103 | 104 | If you want to use the automatic implementation of finder methods, you create an interface that contains the desired methods. 105 | 106 | public interface ProductRepository extends CouchDbCrudRepository { 107 | 108 | List findByComment(Object[] key, ViewParams viewParams); 109 | 110 | ViewResult findByComment(Object[] key, Boolean descending, ViewParams viewParams, Class valueType); 111 | } 112 | 113 | The method parameter `viewParams` allow you to specify arbitrary parameters for the view query. 114 | But usually you will add more parameters to the method signature because they cover your main use cases, here: `key`. 115 | Parameters set in `viewParams` do not override parameters that are contained in the method signature, i.e the key in `viewParams` is ignored. *Attention!* Please read more about method parameter name detection in section Q&A. 116 | 117 | To actually use the methods, your design document must contain a view `findByComment` that is appropriate for your methods. 118 | 119 | { 120 | "_id" : "_design/Product", 121 | "language" : "javascript", 122 | "views" : { 123 | ..., 124 | "findByComment" : { 125 | "map" : "... your custom implementation ...", 126 | "reduce" : "... your custom implementation ..." 127 | } 128 | } 129 | } 130 | 131 | Finally, you create a proxy that replaces the existing repository. The proxy wraps the original repository and adds the custom finder methods. 132 | 133 | CouchDbCrudRepositoryFactory factory = new CouchDbCrudRepositoryFactory(); 134 | productRepository = factory.createRepository(productRepository, null, ProductRepository.class); 135 | 136 | 137 | Custom implementations 138 | ---------------------- 139 | 140 | In some cases you want to provide custom implementations of finder methods. For our example we create an interface called `ProductRepositoryCustom`. 141 | 142 | public interface ProductRepositoryCustom { 143 | List findByComment(Object[] key, Boolean descending); 144 | } 145 | 146 | Then we create an implementation of this interface. 147 | 148 | public class ProductRepositoryCustomImpl implements ProductRepositoryCustom { 149 | 150 | private CouchDbCrudRepository repository; 151 | 152 | public ProductRepositoryCustomImpl(CouchDbCrudRepository repository) { 153 | super(); 154 | this.repository = repository; 155 | } 156 | 157 | public List findByComment(Object[] key, Boolean descending) { 158 | 159 | ViewParams params = new ViewParams(); 160 | params.setReduce(false); 161 | params.setIncludeDocs(true); 162 | params.setReturnType("doc"); 163 | params.setDocumentType(Product.class); 164 | params.setValueType(Object.class); 165 | params.setKeyType(Object.class); 166 | params.setView("findByComment"); 167 | 168 | params.setKey(key); 169 | params.setDescending(descending); 170 | 171 | return repository.find(params); 172 | } 173 | } 174 | 175 | Now the creation of the proxy is a bit different. 176 | 177 | ProductRepositoryCustom customRepository = new ProductRepositoryCustomImpl(productRepository); 178 | 179 | productRepository = factory.createRepository(productRepository, customRepository, ProductRepository.class); 180 | 181 | 182 | 183 | How to develop 184 | ============== 185 | 186 | First start a local CouchDB server (check `http://127.0.0.1:5984/_utils/`). 187 | 188 | Add credentials in `LightCouchTestConfiguration` and `EktorpTestConfiguration` as appropriate: 189 | ```java 190 | properties.setUsername("admin"); 191 | properties.setPassword("admin"); 192 | ``` 193 | 194 | Then run the tests. 195 | ``` 196 | mvn clean install 197 | ``` 198 | 199 | 200 | More Questions and Answers 201 | ========================== 202 | 203 | **Q. Which CouchDB drivers are supported?** 204 | 205 | A. Based on the activity derived from the commit history, this project tried to use the following Java drivers and Scala drivers: 206 | 207 | * [Ektorp](https://github.com/helun/Ektorp). A lot of commits in 2014. 208 | * [LightCouch](https://github.com/lightcouch/LightCouch). A lot of commits in 2014. 209 | * [Sohva](https://github.com/gnieh/sohva). A lot of commits in 2014. 210 | 211 | At the moment Sohva does not provide an API that can be easily used by Java applications. Therefore, Sohva is not used in this project but Ektorp and LightCouch adapters are implemented. 212 | 213 | **Q. The exception com.thoughtworks.paranamer.ParameterNamesNotFoundException is thrown. What can I do?** 214 | 215 | A. [Paranamer](https://github.com/paul-hammant/paranamer) is used to identify the names of method parameters. Go to the project page to get more information about the exception. 216 | 217 | For Java 7 you could try the `AnnotationParanamer`. In this case you have to add JSR-330 to your classpath (javax.inject:javax.inject:1). 218 | 219 | For Java 8 try [Java8Paranamer](https://github.com/rwitzel/Java8Paranamer). Java8Paranamer is a single class you can copy to your source code. Be aware that even for JDK 8 you have to activate a compiler option: `-parameters`. 220 | 221 | Then configure a custom CouchDbCrudRepositoryFactory with your chosen paranamer. 222 | 223 | factory = new CouchDbCrudRepositoryFactory(new ViewParamsMerger(.. your paranamer here...)); 224 | 225 | **Q. Do I have to modify my entity classes to make them compatible with CouchDB?** 226 | 227 | Both Ektorp and LightCouch use JSON serialization frameworks like Jackson and Gson in order to save and load entities. 228 | Therefore, most probably you have to add annotations to your entity classes. 229 | 230 | Additionally, in many cases you will have to add a property for the CouchDB revision. If the IDs of your entities are not of type String, then you have to add a property for the CouchDB ID. 231 | 232 | If your entity classes store ID and revision not in properties with standard names like `_id`, `id` resp. `_rev`, `rev`, `revision`, then use `EntityInformation` to grant CouchRepository access to the ID and the revision. 233 | 234 | **Q. Can I use IDs that are of other type than String, like Integer, a custom class etc.?** 235 | 236 | A. Yes, but natively CouchDB (up to 1.6.1) supports only IDs of type String. Therefore, you have to define a mapping between the CouchDB ID and your domain-specific ID. You will find an example among the unit tests: Look for the class `Exotic` in the source code to find a working example. 237 | 238 | **Q. Are you going to support Spring Data's API for paging and sorting?** 239 | 240 | A. No, there is no plan to support `PagingAndSortingRepository`. 241 | 242 | **Q. Are you going to support XML-based or annotation-based definitions of Spring Data repositories?** 243 | 244 | A. No, there is no plan to do this because the benefit of the declarative approach does not look very big at the moment. A simple Java-based Spring configuration is sufficient. 245 | 246 | **Q. How is the performance of CouchRepository?** 247 | 248 | A. The performance is determined by the performance of the underlying CouchDB drivers and the design of CouchDB at all. Thus, a method like `deleteAll` has to fetch all document IDs from the database to delete the documents. 249 | 250 | **Q. How does the future of CouchRepository look like?** 251 | 252 | A. CouchRepository was build for a single purpose, and it already serves this purpose. Thus, at the moment there are no plans to add more features. But feel free to suggest improvements or to send in patches. 253 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | TODO 3 | - migrate Maven profile coverage 4 | - migrate Maven profile release 5 | */ 6 | 7 | apply plugin: 'java' 8 | apply plugin: 'maven' 9 | apply plugin: 'project-report' 10 | 11 | group = 'com.github.rwitzel' 12 | version = '1.0.0-SNAPSHOT' 13 | 14 | description = """com.github.rwitzel:couchrepository-core""" 15 | 16 | sourceCompatibility = 1.8 17 | targetCompatibility = 1.8 18 | 19 | /* we do not want platform specific builds! Thus let's ignore "warning: [options] bootstrap class path not set in conjunction with -source 1.7" 20 | compileJava { 21 | options.fork = true 22 | options.forkOptions.executable = "C:\Program Files\Java\jdk1.7.0_21_64bit\bin\javac.exe" 23 | } 24 | */ 25 | 26 | repositories { 27 | maven { url "http://repo.maven.apache.org/maven2" } 28 | } 29 | 30 | dependencies { 31 | compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.7' 32 | compile group: 'org.springframework', name: 'spring-core', version:'5.0.12.RELEASE' 33 | compile group: 'org.springframework', name: 'spring-context', version:'5.0.12.RELEASE' /* instead of BOM */ 34 | compile group: 'org.springframework.data', name: 'spring-data-commons', version:'2.0.13.RELEASE' 35 | compile group: 'org.ektorp', name: 'org.ektorp', version:'1.5.0' 36 | compile group: 'org.ektorp', name: 'org.ektorp.spring', version:'1.5.0' 37 | compile group: 'org.lightcouch', name: 'lightcouch', version:'0.1.3' 38 | compile group: 'com.thoughtworks.paranamer', name: 'paranamer', version:'2.7' 39 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version:'2.10.0.pr1' 40 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:'2.10.0.pr1' 41 | compile group: 'org.apache.commons', name: 'commons-lang3', version:'3.3.2' 42 | compile group: 'org.yaml', name: 'snakeyaml', version:'1.14' 43 | testCompile group: 'junit', name: 'junit', version:'4.12' 44 | testCompile group: 'org.mockito', name: 'mockito-core', version:'1.10.8' 45 | testCompile group: 'org.hamcrest', name: 'hamcrest-core', version:'1.3' 46 | testCompile group: 'org.hamcrest', name: 'hamcrest-library', version:'1.3' 47 | testCompile group: 'org.springframework', name: 'spring-test', version:'5.0.12.RELEASE' 48 | testCompile group: 'joda-time', name: 'joda-time', version:'2.6' 49 | testCompile group: 'eu.codearte.catch-exception', name: 'catch-exception', version:'1.3.3' 50 | testCompile group: 'javax.inject', name: 'javax.inject', version:'1' 51 | testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2' 52 | testCompile group: 'org.slf4j', name: 'log4j-over-slf4j', version:'1.7.7' 53 | testCompile group: 'org.slf4j', name: 'jcl-over-slf4j', version:'1.7.7' 54 | } 55 | 56 | task wrapper(type: Wrapper) { 57 | gradleVersion = '4.3' 58 | } 59 | -------------------------------------------------------------------------------- /generate-dependency-info.sh: -------------------------------------------------------------------------------- 1 | 2 | rm -f generated-runtime-deps-list.txt 3 | rm -f generated-runtime-deps-tree.txt 4 | 5 | mvn dependency:list -DincludeScope=runtime -DoutputFile=generated-runtime-deps-list.txt -Dsort=true 6 | mvn dependency:tree -Dscope=runtime -DoutputFile=generated-runtime-deps-tree.txt 7 | -------------------------------------------------------------------------------- /generated-runtime-deps-list.txt: -------------------------------------------------------------------------------- 1 | 2 | The following files have been resolved: 3 | aopalliance:aopalliance:jar:1.0:compile 4 | com.fasterxml.jackson.core:jackson-annotations:jar:2.4.3:compile 5 | com.fasterxml.jackson.core:jackson-core:jar:2.4.3:compile 6 | com.fasterxml.jackson.core:jackson-databind:jar:2.4.3:compile 7 | com.google.code.gson:gson:jar:2.2.4:compile 8 | com.thoughtworks.paranamer:paranamer:jar:2.7:compile 9 | commons-codec:commons-codec:jar:1.6:compile 10 | commons-io:commons-io:jar:2.0.1:compile 11 | commons-logging:commons-logging:jar:1.1.3:compile 12 | net.sourceforge.findbugs:annotations:jar:1.3.2:compile 13 | org.apache.commons:commons-lang3:jar:3.3.2:compile 14 | org.apache.httpcomponents:httpclient-cache:jar:4.3:compile 15 | org.apache.httpcomponents:httpclient:jar:4.3:compile 16 | org.apache.httpcomponents:httpcore:jar:4.3:compile 17 | org.aspectj:aspectjweaver:jar:1.6.9:compile 18 | org.ektorp:org.ektorp.spring:jar:1.4.2:compile 19 | org.ektorp:org.ektorp:jar:1.4.2:compile 20 | org.lightcouch:lightcouch:jar:0.1.3:compile 21 | org.slf4j:slf4j-api:jar:1.7.7:compile 22 | org.springframework.data:spring-data-commons:jar:1.9.1.RELEASE:compile 23 | org.springframework:spring-aop:jar:4.0.7.RELEASE:compile 24 | org.springframework:spring-beans:jar:4.0.7.RELEASE:compile 25 | org.springframework:spring-context:jar:4.0.7.RELEASE:compile 26 | org.springframework:spring-core:jar:4.0.7.RELEASE:compile 27 | org.springframework:spring-expression:jar:4.0.7.RELEASE:compile 28 | org.yaml:snakeyaml:jar:1.14:compile 29 | 30 | -------------------------------------------------------------------------------- /generated-runtime-deps-tree.txt: -------------------------------------------------------------------------------- 1 | com.github.rwitzel:couchrepository-core:jar:0.9.2-SNAPSHOT 2 | +- org.slf4j:slf4j-api:jar:1.7.7:compile 3 | +- org.springframework:spring-core:jar:4.0.7.RELEASE:compile 4 | | \- commons-logging:commons-logging:jar:1.1.3:compile 5 | +- org.springframework.data:spring-data-commons:jar:1.9.1.RELEASE:compile 6 | | \- org.springframework:spring-beans:jar:4.0.7.RELEASE:compile 7 | +- org.ektorp:org.ektorp:jar:1.4.2:compile 8 | | +- org.apache.httpcomponents:httpclient:jar:4.3:compile 9 | | | +- org.apache.httpcomponents:httpcore:jar:4.3:compile 10 | | | \- commons-codec:commons-codec:jar:1.6:compile 11 | | +- org.apache.httpcomponents:httpclient-cache:jar:4.3:compile 12 | | +- commons-io:commons-io:jar:2.0.1:compile 13 | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.3:compile 14 | | \- net.sourceforge.findbugs:annotations:jar:1.3.2:compile 15 | +- org.ektorp:org.ektorp.spring:jar:1.4.2:compile 16 | | +- org.springframework:spring-context:jar:4.0.7.RELEASE:compile 17 | | | +- org.springframework:spring-aop:jar:4.0.7.RELEASE:compile 18 | | | | \- aopalliance:aopalliance:jar:1.0:compile 19 | | | \- org.springframework:spring-expression:jar:4.0.7.RELEASE:compile 20 | | \- org.aspectj:aspectjweaver:jar:1.6.9:compile 21 | +- org.lightcouch:lightcouch:jar:0.1.3:compile 22 | | \- com.google.code.gson:gson:jar:2.2.4:compile 23 | +- com.thoughtworks.paranamer:paranamer:jar:2.7:compile 24 | +- com.fasterxml.jackson.core:jackson-core:jar:2.4.3:compile 25 | +- com.fasterxml.jackson.core:jackson-databind:jar:2.4.3:compile 26 | +- org.apache.commons:commons-lang3:jar:3.3.2:compile 27 | \- org.yaml:snakeyaml:jar:1.14:compile 28 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwitzel/CouchRepository/f4041a6891f68f95c6fb41515c7b9a3158631567/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 30 17:34:11 CEST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.github.rwitzel 7 | couchrepository-core 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 3.0.0 12 | 13 | 14 | 15 | 1.8 16 | UTF-8 17 | 18 | 5.0.19.RELEASE 19 | 2.10.0.pr1 20 | 1.5.0 21 | 1.7.7 22 | 23 | 24 | 25 | ${project.groupId}:${project.artifactId} 26 | https://github.com/rwitzel/CouchRepository 27 | Spring Data repositories for CouchDB 28 | 29 | 30 | scm:git:https://github.com/rwitzel/CouchRepository.git 31 | scm:git:git@github.com:rwitzel/CouchRepository.git 32 | https://github.com/rwitzel/CouchRepository/ 33 | HEAD 34 | 35 | 36 | 37 | 38 | 39 | org.slf4j 40 | slf4j-api 41 | 42 | 43 | 44 | org.springframework 45 | spring-core 46 | 47 | 48 | 49 | org.springframework.data 50 | spring-data-commons 51 | 52 | 53 | 54 | org.ektorp 55 | org.ektorp 56 | true 57 | 58 | 59 | org.ektorp 60 | org.ektorp.spring 61 | true 62 | 63 | 64 | 65 | org.lightcouch 66 | lightcouch 67 | true 68 | 69 | 70 | 71 | com.thoughtworks.paranamer 72 | paranamer 73 | 74 | 75 | 76 | com.fasterxml.jackson.core 77 | jackson-core 78 | 79 | 80 | com.fasterxml.jackson.core 81 | jackson-databind 82 | 83 | 84 | 85 | org.apache.commons 86 | commons-lang3 87 | 88 | 89 | 90 | 91 | junit 92 | junit 93 | test 94 | 95 | 96 | 97 | org.mockito 98 | mockito-core 99 | test 100 | 101 | 102 | 103 | org.hamcrest 104 | hamcrest-core 105 | test 106 | 107 | 108 | org.hamcrest 109 | hamcrest-library 110 | test 111 | 112 | 113 | 114 | org.springframework 115 | spring-test 116 | test 117 | 118 | 119 | 120 | joda-time 121 | joda-time 122 | test 123 | 124 | 125 | 126 | eu.codearte.catch-exception 127 | catch-exception 128 | test 129 | 130 | 131 | 132 | javax.inject 133 | javax.inject 134 | test 135 | 1 136 | 137 | 138 | 139 | 140 | ch.qos.logback 141 | logback-classic 142 | test 143 | 144 | 145 | org.slf4j 146 | log4j-over-slf4j 147 | test 148 | 149 | 150 | org.slf4j 151 | jcl-over-slf4j 152 | test 153 | 154 | 155 | 156 | 157 | org.yaml 158 | snakeyaml 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.springframework 168 | spring-framework-bom 169 | ${spring} 170 | pom 171 | import 172 | 173 | 174 | 175 | org.springframework.data 176 | spring-data-commons 177 | 2.0.13.RELEASE 178 | 179 | 180 | 181 | org.ektorp 182 | org.ektorp 183 | ${ektorp.version} 184 | 185 | 186 | org.ektorp 187 | org.ektorp.spring 188 | ${ektorp.version} 189 | 190 | 191 | 192 | org.lightcouch 193 | lightcouch 194 | 0.1.3 195 | 196 | 197 | 198 | com.fasterxml.jackson.core 199 | jackson-core 200 | ${jackson-2-version} 201 | 202 | 203 | com.fasterxml.jackson.core 204 | jackson-databind 205 | ${jackson-2-version} 206 | 207 | 208 | com.fasterxml.jackson.core 209 | jackson-annotations 210 | ${jackson-2-version} 211 | 212 | 213 | 214 | com.thoughtworks.paranamer 215 | paranamer 216 | 2.7 217 | 218 | 219 | 220 | org.apache.commons 221 | commons-lang3 222 | 3.3.2 223 | 224 | 225 | 226 | junit 227 | junit 228 | 4.13.1 229 | 230 | 231 | 232 | org.mockito 233 | mockito-core 234 | 1.10.8 235 | 236 | 237 | 238 | org.hamcrest 239 | hamcrest-core 240 | 1.3 241 | 242 | 243 | org.hamcrest 244 | hamcrest-library 245 | 1.3 246 | 247 | 248 | 249 | joda-time 250 | joda-time 251 | 2.6 252 | 253 | 254 | 255 | eu.codearte.catch-exception 256 | catch-exception 257 | 1.3.3 258 | 259 | 260 | 261 | 262 | ch.qos.logback 263 | logback-classic 264 | 1.1.2 265 | 266 | 267 | org.slf4j 268 | slf4j-api 269 | ${org.slf4j-version} 270 | 271 | 272 | org.slf4j 273 | log4j-over-slf4j 274 | ${org.slf4j-version} 275 | 276 | 277 | org.slf4j 278 | jcl-over-slf4j 279 | ${org.slf4j-version} 280 | 281 | 282 | 283 | org.yaml 284 | snakeyaml 285 | 1.14 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | org.apache.maven.plugins 298 | maven-compiler-plugin 299 | 3.2 300 | 301 | ${java-version} 302 | ${java-version} 303 | 304 | -g 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | coverage 317 | 318 | 319 | 320 | 321 | org.eluder.coveralls 322 | coveralls-maven-plugin 323 | 3.0.1 324 | 325 | 326 | org.jacoco 327 | jacoco-maven-plugin 328 | 0.7.2.201409121644 329 | 330 | 331 | prepare-agent 332 | 333 | prepare-agent 334 | 335 | 336 | 337 | report 338 | 339 | report 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | release 352 | 353 | 354 | 355 | 356 | 357 | 358 | org.apache.maven.plugins 359 | maven-source-plugin 360 | 2.4 361 | 362 | 363 | attach-sources 364 | 365 | jar-no-fork 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | org.apache.maven.plugins 374 | maven-javadoc-plugin 375 | 2.10.1 376 | 377 | 378 | attach-javadocs 379 | 380 | jar 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | org.apache.maven.plugins 389 | maven-gpg-plugin 390 | 1.5 391 | 392 | 393 | sign-artifacts 394 | verify 395 | 396 | sign 397 | 398 | 399 | 400 | 401 | 402 | 403 | org.sonatype.plugins 404 | nexus-staging-maven-plugin 405 | 1.6.3 406 | true 407 | 408 | ossrh 409 | https://oss.sonatype.org/ 410 | 411 | false 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 422 | 423 | jdk7toolchain 424 | 425 | 426 | 427 | 428 | 429 | org.apache.maven.plugins 430 | maven-toolchains-plugin 431 | 1.0 432 | 433 | 434 | validate 435 | 436 | toolchain 437 | 438 | 439 | 440 | 441 | 442 | 443 | 1.7 444 | sun 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 468 | 469 | ossrh 470 | https://oss.sonatype.org/content/repositories/snapshots 471 | 472 | 473 | 482 | 483 | ossrh 484 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 485 | 486 | 487 | 488 | 489 | 490 | Apache 2 491 | http://www.apache.org/licenses/LICENSE-2.0.txt 492 | repo 493 | A business-friendly OSS license 494 | 495 | 496 | 497 | 498 | 499 | rwitzel 500 | Rodrigo Witzel 501 | rwitzel75@googlemail.com 502 | 503 | 504 | 505 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'couchrepository-core' 2 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/CouchDbCrudRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | /** 8 | * This {@link CrudRepository} for CouchDB databases allows you to query views. 9 | * 10 | * @author rwitzel 11 | * @param See type parameter in {@link CrudRepository} 12 | * @param See type parameter in {@link CrudRepository} 13 | */ 14 | public interface CouchDbCrudRepository extends CrudRepository { 15 | 16 | /** 17 | * Queries the database with the given parameters. 18 | * 19 | * @param viewParams the query parameters 20 | * @param the return type, depends on {@link ViewParams#getReturnType()}. 21 | * @return Returns the result of the query. The type of the return value depends on {@link ViewParams#getReturnType()}. 22 | */ 23 | R find(ViewParams viewParams); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/CouchDbCrudRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.lang.reflect.Proxy; 4 | 5 | import org.springframework.data.repository.core.support.RepositoryFactorySupport; 6 | 7 | import com.github.rwitzel.couchrepository.internal.QueryMethodHandler; 8 | import com.github.rwitzel.couchrepository.internal.ViewParamsMerger; 9 | 10 | /** 11 | * Creates {@link CouchDbCrudRepository repositories} that automatically implement a specific interface. Comparable to 12 | * Spring Data's {@link RepositoryFactorySupport}. 13 | * 14 | * @author rwitzel 15 | */ 16 | public class CouchDbCrudRepositoryFactory { 17 | 18 | private ViewParamsMerger viewParamsMerger; 19 | 20 | public CouchDbCrudRepositoryFactory() { 21 | this(new ViewParamsMerger()); 22 | } 23 | 24 | public CouchDbCrudRepositoryFactory(ViewParamsMerger viewParamsMerger) { 25 | super(); 26 | this.viewParamsMerger = viewParamsMerger; 27 | } 28 | 29 | /** 30 | * Creates a repository that implements the given interface. 31 | * 32 | * @param underlyingRepository 33 | * the underlying repository 34 | * @param customRepository 35 | * An object that implements custom finder methods. Can be null. 36 | * @param repositoryType 37 | * the interface of the returned repository 38 | * @param the return type 39 | * @return Returns the created repository. 40 | */ 41 | @SuppressWarnings({ "rawtypes", "unchecked" }) 42 | public T createRepository(CouchDbCrudRepository underlyingRepository, Object customRepository, 43 | Class repositoryType) { 44 | 45 | QueryMethodHandler handler = new QueryMethodHandler(underlyingRepository, customRepository, 46 | viewParamsMerger); 47 | return (T) Proxy.newProxyInstance(repositoryType.getClassLoader(), new Class[] { repositoryType }, handler); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/EntityInformation.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** 7 | * This class allows access to the ID and the revision of entities of a specific type. 8 | * 9 | * @author rwitzel 10 | * @param 11 | * the type of the handled entities 12 | * @param 13 | * the type of the ID of the handled entities 14 | */ 15 | public interface EntityInformation { 16 | 17 | /** 18 | * Converts the given ID to a CouchDB identifier. 19 | * 20 | * @param id the ID of the entity 21 | * @return Returns the transformed identifier. 22 | */ 23 | String toCouchId(ID id); 24 | 25 | List toCouchIds(Iterable iter); 26 | 27 | /** 28 | * @param entity the entity 29 | * @return Returns the CouchDb identifier of the entity. 30 | */ 31 | String getCouchId(T entity); 32 | 33 | /** 34 | * Sets the identifier of the entity, i.e. transforms the given CouchDb identifier if necessary. 35 | * 36 | * @param entity the entity 37 | * @param couchId the CouchID for the entity 38 | */ 39 | void setId(T entity, String couchId); 40 | 41 | /** 42 | * @param entity the entity 43 | * @return Returns the revision of the given entity. 44 | */ 45 | String getRev(T entity); 46 | 47 | /** 48 | * Sets the revision in the given entity. 49 | * 50 | * @param entity the entity 51 | * @param rev the new revision for the entity 52 | */ 53 | void setRev(T entity, String rev); 54 | 55 | /** 56 | * @param entity the entity 57 | * @return Returns true if the given entity is new, i.e. it does not have a revision. 58 | */ 59 | boolean isNew(T entity); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/ViewParams.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import com.github.rwitzel.couchrepository.api.exceptions.UnsupportedViewParameterException; 4 | 5 | /** 6 | * This POJO represents 7 | *
    8 | *
  • the parameters for a view query, see CouchDB 1.7.0 API reference 10.5.4. 10 | * /db/_design/design-doc/_view/view-name, 11 | *
  • the name of the view, 12 | *
  • the name of the design document that contains the view, 13 | *
  • the return types of the view (key, value, document). 14 | *
15 | *

16 | * The underlying driver must not support all of the listed parameters. In this case a 17 | * {@link UnsupportedViewParameterException} might be thrown. 18 | *

19 | * Additionally, the name of the view and its design document can be set. 20 | * 21 | * @author rwitzel 22 | */ 23 | public class ViewParams { 24 | 25 | private Boolean conflicts; 26 | 27 | private Boolean descending; 28 | 29 | private Object endKey; 30 | 31 | private String endKeyDocId; 32 | 33 | private Boolean group; 34 | 35 | private Integer groupLevel; 36 | 37 | private Boolean includeDocs; 38 | 39 | private Boolean attachments; 40 | 41 | private Boolean attEncodingInfo; 42 | 43 | private Boolean inclusiveEnd; 44 | 45 | private Object key; 46 | 47 | private Integer limit; 48 | 49 | private Boolean reduce; 50 | 51 | private Integer skip; 52 | 53 | private String stale; 54 | 55 | private Object startKey; 56 | 57 | private String startKeyDocId; 58 | 59 | private Boolean updateSeq; 60 | 61 | /** 62 | * The name of the design document, excluding the prefix "_design". 63 | *

64 | * Only set if the design document deviates from the default. 65 | */ 66 | private String designDocument; 67 | 68 | /** 69 | * The name of the view. 70 | *

71 | * Only set if the view deviates from the default. 72 | */ 73 | private String view; 74 | 75 | /** 76 | * The type of keys in a view. 77 | */ 78 | private Class keyType; 79 | 80 | /** 81 | * The type of values in a view. 82 | */ 83 | private Class valueType; 84 | 85 | /** 86 | * The type of documents in a view (if documents are included). 87 | */ 88 | private Class documentType; 89 | 90 | /** 91 | * If null, then the return type of the query will be {@link ViewResult}. If "value" or "key" or "doc" or "id" 92 | * then a list of values or keys or documents or document IDs will be returned. 93 | */ 94 | private String returnType; 95 | 96 | public Boolean getConflicts() { 97 | return conflicts; 98 | } 99 | 100 | public void setConflicts(Boolean conflicts) { 101 | this.conflicts = conflicts; 102 | } 103 | 104 | public Boolean getDescending() { 105 | return descending; 106 | } 107 | 108 | public void setDescending(Boolean descending) { 109 | this.descending = descending; 110 | } 111 | 112 | public Object getEndKey() { 113 | return endKey; 114 | } 115 | 116 | public void setEndKey(Object endKey) { 117 | this.endKey = endKey; 118 | } 119 | 120 | public String getEndKeyDocId() { 121 | return endKeyDocId; 122 | } 123 | 124 | public void setEndKeyDocId(String endKeyDocId) { 125 | this.endKeyDocId = endKeyDocId; 126 | } 127 | 128 | public Boolean getGroup() { 129 | return group; 130 | } 131 | 132 | public void setGroup(Boolean group) { 133 | this.group = group; 134 | } 135 | 136 | public Integer getGroupLevel() { 137 | return groupLevel; 138 | } 139 | 140 | public void setGroupLevel(Integer groupLevel) { 141 | this.groupLevel = groupLevel; 142 | } 143 | 144 | public Boolean getIncludeDocs() { 145 | return includeDocs; 146 | } 147 | 148 | public void setIncludeDocs(Boolean includeDocs) { 149 | this.includeDocs = includeDocs; 150 | } 151 | 152 | public Boolean getAttachments() { 153 | return attachments; 154 | } 155 | 156 | public void setAttachments(Boolean attachments) { 157 | this.attachments = attachments; 158 | } 159 | 160 | public Boolean getAttEncodingInfo() { 161 | return attEncodingInfo; 162 | } 163 | 164 | public void setAttEncodingInfo(Boolean attEncodingInfo) { 165 | this.attEncodingInfo = attEncodingInfo; 166 | } 167 | 168 | public Boolean getInclusiveEnd() { 169 | return inclusiveEnd; 170 | } 171 | 172 | public void setInclusiveEnd(Boolean inclusiveEnd) { 173 | this.inclusiveEnd = inclusiveEnd; 174 | } 175 | 176 | public Object getKey() { 177 | return key; 178 | } 179 | 180 | public void setKey(Object key) { 181 | this.key = key; 182 | } 183 | 184 | public Integer getLimit() { 185 | return limit; 186 | } 187 | 188 | public void setLimit(Integer limit) { 189 | this.limit = limit; 190 | } 191 | 192 | public Boolean getReduce() { 193 | return reduce; 194 | } 195 | 196 | public void setReduce(Boolean reduce) { 197 | this.reduce = reduce; 198 | } 199 | 200 | public Integer getSkip() { 201 | return skip; 202 | } 203 | 204 | public void setSkip(Integer skip) { 205 | this.skip = skip; 206 | } 207 | 208 | public String getStale() { 209 | return stale; 210 | } 211 | 212 | public void setStale(String stale) { 213 | this.stale = stale; 214 | } 215 | 216 | public Object getStartKey() { 217 | return startKey; 218 | } 219 | 220 | public void setStartKey(Object startKey) { 221 | this.startKey = startKey; 222 | } 223 | 224 | public String getStartKeyDocId() { 225 | return startKeyDocId; 226 | } 227 | 228 | public void setStartKeyDocId(String startKeyDocId) { 229 | this.startKeyDocId = startKeyDocId; 230 | } 231 | 232 | public Boolean getUpdateSeq() { 233 | return updateSeq; 234 | } 235 | 236 | public void setUpdateSeq(Boolean updateSeq) { 237 | this.updateSeq = updateSeq; 238 | } 239 | 240 | public String getDesignDocument() { 241 | return designDocument; 242 | } 243 | 244 | public void setDesignDocument(String designDocument) { 245 | this.designDocument = designDocument; 246 | } 247 | 248 | public String getView() { 249 | return view; 250 | } 251 | 252 | public void setView(String view) { 253 | this.view = view; 254 | } 255 | 256 | public Class getKeyType() { 257 | return keyType; 258 | } 259 | 260 | public void setKeyType(Class keyType) { 261 | this.keyType = keyType; 262 | } 263 | 264 | public Class getValueType() { 265 | return valueType; 266 | } 267 | 268 | public void setValueType(Class valueType) { 269 | this.valueType = valueType; 270 | } 271 | 272 | public Class getDocumentType() { 273 | return documentType; 274 | } 275 | 276 | public void setDocumentType(Class documentType) { 277 | this.documentType = documentType; 278 | } 279 | 280 | public String getReturnType() { 281 | return returnType; 282 | } 283 | 284 | public void setReturnType(String returnType) { 285 | this.returnType = returnType; 286 | } 287 | 288 | @Override 289 | public String toString() { 290 | return "ViewParams [conflicts=" + conflicts + ", descending=" + descending + ", endKey=" + endKey 291 | + ", endKeyDocId=" + endKeyDocId + ", group=" + group + ", groupLevel=" + groupLevel + ", includeDocs=" 292 | + includeDocs + ", attachments=" + attachments + ", attEncodingInfo=" + attEncodingInfo 293 | + ", inclusiveEnd=" + inclusiveEnd + ", key=" + key + ", limit=" + limit + ", reduce=" + reduce 294 | + ", skip=" + skip + ", stale=" + stale + ", startKey=" + startKey + ", startKeyDocId=" + startKeyDocId 295 | + ", updateSeq=" + updateSeq + ", designDocument=" + designDocument + ", view=" + view + ", keyType=" 296 | + keyType + ", valueType=" + valueType + ", documentType=" + documentType + ", returnType=" 297 | + returnType + "]"; 298 | } 299 | 300 | } 301 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/ViewResult.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * This POJO represents the result of a view query, see CouchDB 1.7.0 API reference 10.5.4. 9 | * /db/_design/design-doc/_view/view-name. 10 | * 11 | * @author rwitzel 12 | */ 13 | public class ViewResult { 14 | 15 | protected long totalRows; 16 | 17 | protected long updateSeq; 18 | 19 | protected int offset; 20 | 21 | protected List rows; 22 | 23 | public long getTotalRows() { 24 | return totalRows; 25 | } 26 | 27 | public void setTotalRows(long totalRows) { 28 | this.totalRows = totalRows; 29 | } 30 | 31 | public long getUpdateSeq() { 32 | return updateSeq; 33 | } 34 | 35 | public void setUpdateSeq(long updateSeq) { 36 | this.updateSeq = updateSeq; 37 | } 38 | 39 | public int getOffset() { 40 | return offset; 41 | } 42 | 43 | public void setOffset(int offset) { 44 | this.offset = offset; 45 | } 46 | 47 | public List getRows() { 48 | if (rows == null) { 49 | rows = new ArrayList(); 50 | } 51 | return rows; 52 | } 53 | 54 | public void setRows(List rows) { 55 | this.rows = rows; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ViewResult [totalRows=" + totalRows + ", updateSeq=" + updateSeq + ", offset=" + offset + ", rows=" 61 | + rows + "]"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/ViewResultRow.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | /** 4 | * The row in a {@link ViewResult}. 5 | * 6 | * @author rwitzel 7 | */ 8 | public class ViewResultRow { 9 | 10 | /** 11 | * The CouchDB ID of the document. 12 | */ 13 | protected String id; 14 | 15 | protected Object key; 16 | 17 | protected Object value; 18 | 19 | protected Object doc; 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public void setId(String id) { 26 | this.id = id; 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | public K getKey() { 31 | return (K) key; 32 | } 33 | 34 | public void setKey(Object key) { 35 | this.key = key; 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | public V getValue() { 40 | return (V) value; 41 | } 42 | 43 | public void setValue(Object value) { 44 | this.value = value; 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | public D getDoc() { 49 | return (D) doc; 50 | } 51 | 52 | public void setDoc(Object doc) { 53 | this.doc = doc; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "ViewResultRow [id=" + id + ", key=" + key + ", value=" + value + ", doc=" + doc + "]"; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/exceptions/BulkOperationError.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api.exceptions; 2 | 3 | /** 4 | * Part of {@link BulkOperationException}. 5 | * 6 | * @author rwitzel 7 | */ 8 | public class BulkOperationError { 9 | 10 | private String id; 11 | 12 | private String rev; 13 | 14 | private String error; 15 | 16 | private String reason; 17 | 18 | public BulkOperationError() { 19 | super(); 20 | } 21 | 22 | public BulkOperationError(String id, String rev, String error, String reason) { 23 | super(); 24 | this.id = id; 25 | this.rev = rev; 26 | this.error = error; 27 | this.reason = reason; 28 | } 29 | 30 | public String getId() { 31 | return id; 32 | } 33 | 34 | public void setId(String id) { 35 | this.id = id; 36 | } 37 | 38 | public String getRev() { 39 | return rev; 40 | } 41 | 42 | public void setRev(String rev) { 43 | this.rev = rev; 44 | } 45 | 46 | public String getError() { 47 | return error; 48 | } 49 | 50 | public void setError(String error) { 51 | this.error = error; 52 | } 53 | 54 | public String getReason() { 55 | return reason; 56 | } 57 | 58 | public void setReason(String reason) { 59 | this.reason = reason; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "BulkOperationError [id=" + id + ", rev=" + rev + ", error=" + error + ", reason=" + reason + "]"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/exceptions/BulkOperationException.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api.exceptions; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Thrown if an error happens during a bulk operation. 7 | * 8 | * @author rwitzel 9 | */ 10 | public class BulkOperationException extends RuntimeException { 11 | 12 | private static final long serialVersionUID = 8946510245961827774L; 13 | 14 | private List errors; 15 | 16 | public BulkOperationException(String message, List errors) { 17 | super(message); 18 | this.errors = errors; 19 | } 20 | 21 | public List getErrors() { 22 | return errors; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "BulkOperationException [errors=" + errors + ", getMessage()=" + getMessage() + "]"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/api/exceptions/UnsupportedViewParameterException.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api.exceptions; 2 | 3 | import com.github.rwitzel.couchrepository.api.ViewParams; 4 | 5 | /** 6 | * This {@link IllegalArgumentException} is thrown when the underlying Couchdb Driver does not supported a value set in 7 | * {@link ViewParams}. 8 | * 9 | * @author rwitzel 10 | */ 11 | public class UnsupportedViewParameterException extends IllegalArgumentException { 12 | 13 | private static final long serialVersionUID = 580055614837609769L; 14 | 15 | public UnsupportedViewParameterException(String unsupportedViewParameter) { 16 | super("view parameter " + unsupportedViewParameter + " is not supported"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/ektorp/EktorpCouchViewConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.ektorp; 2 | 3 | import org.ektorp.ViewQuery; 4 | 5 | import com.github.rwitzel.couchrepository.api.ViewParams; 6 | import com.github.rwitzel.couchrepository.api.exceptions.UnsupportedViewParameterException; 7 | 8 | /** 9 | * This internal class assists in configuring a {@link ViewQuery}. 10 | * 11 | * @author rwitzel 12 | */ 13 | public class EktorpCouchViewConfigurer { 14 | 15 | /** 16 | * Configures Ektorp's {@link ViewQuery} with the given {@link ViewParams}. 17 | * 18 | * @param view the Ektorp object to be configured 19 | * @param params the parameters that shall be used 20 | */ 21 | public void configure(ViewQuery view, ViewParams params) { 22 | if (params.getAttachments() != null) { 23 | throw new UnsupportedViewParameterException("attachments"); 24 | } 25 | if (params.getAttEncodingInfo() != null) { 26 | throw new UnsupportedViewParameterException("att_encoding_info"); 27 | } 28 | if (params.getConflicts() != null) { 29 | throw new UnsupportedViewParameterException("conflicts"); 30 | } 31 | if (params.getDescending() != null) { 32 | view.descending(params.getDescending()); 33 | } 34 | if (params.getGroup() != null) { 35 | view.group(params.getGroup()); 36 | } 37 | if (params.getIncludeDocs() != null) { 38 | view.includeDocs(params.getIncludeDocs()); 39 | } 40 | if (params.getInclusiveEnd() != null) { 41 | view.inclusiveEnd(params.getInclusiveEnd()); 42 | } 43 | if (params.getReduce() != null) { 44 | view.reduce(params.getReduce()); 45 | } 46 | if (params.getUpdateSeq() != null) { 47 | view.updateSeq(params.getUpdateSeq()); 48 | } 49 | if (params.getEndKey() != null) { 50 | view.endKey(params.getEndKey()); 51 | } 52 | if (params.getEndKeyDocId() != null) { 53 | view.endDocId(params.getEndKeyDocId()); 54 | } 55 | if (params.getGroupLevel() != null) { 56 | view.groupLevel(params.getGroupLevel()); 57 | } 58 | if (params.getKey() != null) { 59 | view.key(params.getKey()); 60 | } 61 | if (params.getLimit() != null) { 62 | view.limit(params.getLimit()); 63 | } 64 | if (params.getSkip() != null) { 65 | view.skip(params.getSkip()); 66 | } 67 | if (params.getStale() != null) { 68 | if ("ok".equals(params.getStale())) { 69 | view.staleOk(true); 70 | } else if ("update_after".equals(params.getStale())) { 71 | view.staleOkUpdateAfter(); 72 | } 73 | } 74 | if (params.getStartKey() != null) { 75 | view.startKey(params.getStartKey()); 76 | } 77 | if (params.getStartKeyDocId() != null) { 78 | view.startDocId(params.getStartKeyDocId()); 79 | } 80 | 81 | // 82 | 83 | if (params.getDesignDocument() != null) { 84 | view.designDocId("_design/" + params.getDesignDocument()); 85 | } 86 | if (params.getView() != null) { 87 | view.viewName(params.getView()); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/ektorp/EktorpCrudRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.ektorp; 2 | 3 | import static com.github.rwitzel.couchrepository.internal.AdapterUtils.toList; 4 | import static com.github.rwitzel.couchrepository.internal.AdapterUtils.transformViewResult; 5 | 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.ektorp.CouchDbConnector; 16 | import org.ektorp.DocumentNotFoundException; 17 | import org.ektorp.DocumentOperationResult; 18 | import org.ektorp.ViewQuery; 19 | import org.ektorp.ViewResult.Row; 20 | import org.ektorp.impl.NameConventions; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | import org.springframework.util.Assert; 24 | 25 | import com.fasterxml.jackson.core.JsonProcessingException; 26 | import com.fasterxml.jackson.databind.JsonNode; 27 | import com.fasterxml.jackson.databind.ObjectMapper; 28 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepository; 29 | import com.github.rwitzel.couchrepository.api.EntityInformation; 30 | import com.github.rwitzel.couchrepository.api.ViewParams; 31 | import com.github.rwitzel.couchrepository.api.ViewResult; 32 | import com.github.rwitzel.couchrepository.api.ViewResultRow; 33 | import com.github.rwitzel.couchrepository.api.exceptions.BulkOperationError; 34 | import com.github.rwitzel.couchrepository.api.exceptions.BulkOperationException; 35 | 36 | /** 37 | * This implementation of {@link CouchDbCrudRepository} uses Ektorp's {@link CouchDbConnector}. 38 | *

39 | * This implementation requires a special view in a design document. EXAMPLE: 40 | "views" : { 41 | "by_id" : { 42 | "map" : "function(doc) { if(doc.type == '...') {emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", 43 | "reduce" : "_count" 44 | } 45 | } 46 | 47 | *

48 | * Take care when using {@link #deleteAll()} because it loads the IDs and revisions of all documents of the 49 | * abovementioned view at once, then deletes the documents. 50 | * 51 | * @author rwitzel 52 | */ 53 | public class EktorpCrudRepository implements CouchDbCrudRepository { 54 | 55 | protected Logger logger = LoggerFactory.getLogger(EktorpCrudRepository.class); 56 | 57 | protected EktorpCouchViewConfigurer viewBuilder = new EktorpCouchViewConfigurer(); 58 | 59 | protected Class type; 60 | 61 | protected EntityInformation ei; 62 | 63 | protected boolean allOrNothing; 64 | 65 | protected CouchDbConnector db; 66 | 67 | protected ObjectMapper objectMapper; 68 | 69 | public EktorpCrudRepository(Class type, CouchDbConnector db) { 70 | this(type, false, db, new ObjectMapper(), new EktorpEntityInformation()); 71 | } 72 | 73 | public EktorpCrudRepository(Class type, CouchDbConnector db, EntityInformation ei) { 74 | this(type, false, db, new ObjectMapper(), ei); 75 | } 76 | 77 | public EktorpCrudRepository(Class type, boolean allOrNothing, CouchDbConnector db, ObjectMapper objectMapper, 78 | EntityInformation ei) { 79 | super(); 80 | this.type = type; 81 | this.ei = ei; 82 | this.allOrNothing = allOrNothing; 83 | this.db = db; 84 | this.objectMapper = objectMapper; 85 | } 86 | 87 | public S save(S entity) { 88 | 89 | Assert.notNull(entity, "The given entity must not be null."); 90 | 91 | if (ei.isNew(entity)) { 92 | db.create(entity); 93 | } else { 94 | db.update(entity); 95 | } 96 | 97 | return entity; // Hint: the revision is already added resp. updated by Ektorp 98 | } 99 | 100 | public Iterable saveAll(Iterable entities) { 101 | 102 | Assert.notNull(entities, "The given list of entities must not be null."); 103 | 104 | executeBulk(toList(entities)); 105 | 106 | return entities; // Hint: the revision is already added resp. updated by Ektorp 107 | } 108 | 109 | public Optional findById(ID id) { 110 | 111 | Assert.notNull(id, "The given ID must not be null."); 112 | 113 | try { 114 | return Optional.of(db.get(type, ei.toCouchId(id))); 115 | } catch (DocumentNotFoundException e) { 116 | logger.debug("document with ID " + id + " not found", e); 117 | return Optional.ofNullable(null); 118 | } 119 | } 120 | 121 | @Override 122 | public boolean existsById(ID id) { 123 | 124 | Assert.notNull(id, "The given ID must not be null."); 125 | 126 | return db.contains(ei.toCouchId(id)); 127 | } 128 | 129 | public Iterable findAll() { 130 | ViewQuery viewQuery = createQuery("by_id").reduce(false).includeDocs(true); 131 | return db.queryView(viewQuery, type); 132 | } 133 | 134 | public Iterable findAllById(Iterable ids) { 135 | 136 | Assert.notNull(ids, "The given list of IDs must not be null."); 137 | 138 | ViewQuery q = createQuery("by_id").keys(ei.toCouchIds(ids)).reduce(false).includeDocs(true); 139 | return db.queryView(q, type); 140 | } 141 | 142 | public long count() { 143 | 144 | ViewQuery viewQuery = createQuery("by_id").reduce(true); 145 | org.ektorp.ViewResult viewResult = db.queryView(viewQuery); 146 | if (viewResult.getRows().size() == 0) { 147 | // there are no documents -> there is no sum 148 | return 0; 149 | } else { 150 | // the sum is in the returned row (there is only one row) 151 | return viewResult.getRows().get(0).getValueAsInt(); 152 | } 153 | } 154 | 155 | @SuppressWarnings("rawtypes") 156 | public void deleteById(ID id) { 157 | 158 | Assert.notNull(id, "The given ID must not be null."); 159 | 160 | ViewQuery viewQuery = createQuery("by_id").reduce(false).includeDocs(false).key(ei.toCouchId(id)); 161 | List results = db.queryView(viewQuery, Map.class); 162 | if (results.size() != 0) { 163 | db.delete((String) results.get(0).get("_id"), (String) results.get(0).get("_rev")); 164 | } 165 | } 166 | 167 | public void delete(T entity) { 168 | 169 | Assert.notNull(entity, "The given entity must not be null."); 170 | 171 | db.delete(ei.getCouchId(entity), ei.getRev(entity)); 172 | } 173 | 174 | public void deleteAll(Iterable entities) { 175 | 176 | Assert.notNull(entities, "The given list of entities must not be null."); 177 | 178 | Collection> collection = new ArrayList>(); 179 | for (T entity : entities) { 180 | collection.add(createBulkDeleteDocument(ei.getCouchId(entity), ei.getRev(entity))); 181 | } 182 | 183 | executeBulk(collection); 184 | } 185 | 186 | protected Map createBulkDeleteDocument(String id, String revision) { 187 | Map map = new HashMap(3); 188 | map.put("_id", id); 189 | map.put("_rev", revision); 190 | map.put("_deleted", true); 191 | return map; 192 | } 193 | 194 | protected void executeBulk(Collection collection) { 195 | 196 | logger.debug(collection.size() + " documents are going to be processed ..."); 197 | 198 | List results; 199 | if (allOrNothing) { 200 | results = db.executeAllOrNothing(collection); 201 | } else { 202 | results = db.executeBulk(collection); 203 | } 204 | 205 | // handle errors 206 | List errors = new ArrayList(); 207 | List messages = new ArrayList(); 208 | for (DocumentOperationResult result : results) { 209 | if (result.getError() != null) { 210 | errors.add(new BulkOperationError(result.getId(), result.getRevision(), result.getError(), result 211 | .getReason())); 212 | messages.add(result.toString()); 213 | } 214 | } 215 | if (errors.size() > 0) { 216 | throw new BulkOperationException(StringUtils.join(messages, ","), errors); 217 | } else { 218 | logger.debug("All documents have been processed."); 219 | } 220 | } 221 | 222 | @SuppressWarnings({ "rawtypes", "unchecked" }) 223 | public void deleteAll() { 224 | 225 | ViewQuery viewQuery = createQuery("by_id").reduce(false).includeDocs(false); 226 | List> results = ((List) db.queryView(viewQuery, HashMap.class)); 227 | for (Map result : results) { 228 | result.put("_deleted", true); 229 | } 230 | 231 | executeBulk(results); 232 | } 233 | 234 | protected ViewQuery createQuery(String viewName) { 235 | return new ViewQuery().dbPath(db.path()).designDocId(NameConventions.designDocName(type)).viewName(viewName); 236 | } 237 | 238 | public R find(ViewParams viewParams) { 239 | 240 | ViewQuery viewQuery = new ViewQuery().dbPath(db.path()); 241 | viewBuilder.configure(viewQuery, viewParams); 242 | if (viewQuery.getDesignDocId() == null) { 243 | viewQuery.designDocId(NameConventions.designDocName(type)); 244 | } 245 | 246 | org.ektorp.ViewResult ektorpViewResult = db.queryView(viewQuery); 247 | 248 | ViewResult viewResult = toViewResult(ektorpViewResult, viewParams); 249 | 250 | return transformViewResult(viewResult, viewParams.getReturnType()); 251 | } 252 | 253 | /** 254 | * Transforms a Ektorp-specific view result to CouchRepository's view result. 255 | * 256 | * @param viewResult 257 | * the Ektorp-specific view result 258 | * @param viewParams 259 | * the parameters inform about the return type 260 | * @return Returns the transformed view result. 261 | */ 262 | protected ViewResult toViewResult(org.ektorp.ViewResult viewResult, ViewParams viewParams) { 263 | ViewResult result = new ViewResult(); 264 | result.setOffset(result.getOffset()); 265 | result.setTotalRows(viewResult.getTotalRows()); 266 | result.setUpdateSeq(viewResult.getUpdateSeq()); 267 | 268 | List rows = viewResult.getRows(); 269 | List resultRows = new ArrayList(); 270 | for (Row row : rows) { 271 | ViewResultRow resultRow = new ViewResultRow(); 272 | resultRow.setId(id(row)); 273 | resultRow.setDoc(toObject(row.getDocAsNode(), viewParams.getDocumentType())); 274 | resultRow.setKey(toObject(row.getKeyAsNode(), viewParams.getKeyType())); 275 | resultRow.setValue(toObject(row.getValueAsNode(), viewParams.getValueType())); 276 | resultRows.add(resultRow); 277 | } 278 | 279 | result.setRows(resultRows); 280 | 281 | return result; 282 | } 283 | 284 | private String id(Row row) { 285 | try { 286 | return row.getId(); 287 | } catch (NullPointerException e) { 288 | return null; // happens when reduce = true -> TODO Ektorp feature request -> pull request is sent 289 | } 290 | } 291 | 292 | protected Object toObject(JsonNode jsonNode, Class valueType) { 293 | if (jsonNode == null || valueType == null) { 294 | return null; 295 | } else { 296 | try { 297 | return objectMapper.treeToValue(jsonNode, valueType); 298 | } catch (JsonProcessingException e) { 299 | throw new RuntimeException(e); 300 | } 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/ektorp/EktorpEntityInformation.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.ektorp; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.ektorp.util.Documents; 8 | 9 | import com.github.rwitzel.couchrepository.api.EntityInformation; 10 | 11 | /** 12 | * This {@link EntityInformation} is only for entities with ID of type String. 13 | * 14 | * @author rwitzel 15 | */ 16 | public class EktorpEntityInformation implements EntityInformation { 17 | 18 | @Override 19 | public String toCouchId(ID id) { 20 | if (id == null) { 21 | return null; 22 | } else if (id instanceof CharSequence) { 23 | return ((CharSequence) id).toString(); 24 | } else { 25 | throw new RuntimeException("unsupported type. Should you use another implementation of " 26 | + EntityInformation.class + "?"); 27 | } 28 | } 29 | 30 | @Override 31 | public List toCouchIds(Iterable iter) { 32 | List list = new ArrayList(); 33 | for (ID item : iter) { 34 | list.add(toCouchId(item)); 35 | } 36 | return list; 37 | } 38 | 39 | @Override 40 | public String getCouchId(T entity) { 41 | return Documents.getId(entity); 42 | } 43 | 44 | @Override 45 | public String getRev(T entity) { 46 | return Documents.getRevision(entity); 47 | } 48 | 49 | @Override 50 | public void setId(T entity, String couchId) { 51 | throw new UnsupportedOperationException("not needed. therefore, not implemented"); 52 | } 53 | 54 | @Override 55 | public void setRev(T entity, String rev) { 56 | throw new UnsupportedOperationException("not needed. therefore, not implemented"); 57 | } 58 | 59 | public boolean isNew(T entity) { 60 | return Documents.isNew(entity); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/internal/AdapterUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.internal; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.github.rwitzel.couchrepository.api.ViewResult; 7 | import com.github.rwitzel.couchrepository.api.ViewResultRow; 8 | 9 | /** 10 | * Transforms Spring Data specific values to CouchDB driver specific values and the other way around. 11 | * 12 | * @author rwitzel 13 | */ 14 | public class AdapterUtils { 15 | 16 | /** 17 | * Copies the elements of {@link Iterable} into a list and returns them. 18 | * 19 | * @param iter the iterable that contains the elements 20 | * @param the type of the elements 21 | * @return Returns the elements of the {@link Iterable}. 22 | */ 23 | public static List toList(Iterable iter) { 24 | List list = new ArrayList(); 25 | for (E item : iter) { 26 | list.add(item); 27 | } 28 | return list; 29 | } 30 | 31 | /** 32 | * Transforms the view result to a list of keys or values or documents or IDs depending on the given return type. If 33 | * the return type is null, the view result is not transformed. 34 | * 35 | * @param viewResult the original view result 36 | * @param returnType 37 | * null or "key or "value" or "doc" or "id" 38 | * @param the type of the elements 39 | * @return Returns the transformed view result. 40 | */ 41 | @SuppressWarnings("unchecked") 42 | public static E transformViewResult(ViewResult viewResult, String returnType) { 43 | 44 | if (returnType == null) { 45 | return (E) viewResult; 46 | } else { 47 | List list = new ArrayList(); 48 | for (ViewResultRow row : viewResult.getRows()) { 49 | 50 | Object obj; 51 | if (returnType.equals("key")) { 52 | obj = row.getKey(); 53 | } else if (returnType.equals("value")) { 54 | obj = row.getValue(); 55 | } else if (returnType.equals("doc")) { 56 | obj = row.getDoc(); 57 | } else if (returnType.equals("id")) { 58 | obj = row.getId(); 59 | } else { 60 | throw new IllegalArgumentException("not supported return type: " + returnType); 61 | } 62 | list.add(obj); 63 | } 64 | return (E) list; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/internal/QueryMethodHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.internal; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | import org.springframework.util.ReflectionUtils; 8 | 9 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepository; 10 | import com.github.rwitzel.couchrepository.api.ViewParams; 11 | 12 | /** 13 | * This {@link InvocationHandler} delegates to a {@link #customRepository repository with custom method implementations} 14 | * or the {@link #crudRepository underlying CRUD repository} if these repositories have implemented a method with the 15 | * same name as the called method. If there is not such a method, the handler collects all arguments in a 16 | * {@link ViewParams} object and calls {@link CouchDbCrudRepository#find(ViewParams)} on the underlying CRUD repository. 17 | * 18 | * @author rwitzel 19 | */ 20 | @SuppressWarnings("rawtypes") 21 | public class QueryMethodHandler implements InvocationHandler { 22 | 23 | protected CouchDbCrudRepository crudRepository; 24 | 25 | protected Object customRepository; 26 | 27 | protected ViewParamsMerger viewParamsMerger; 28 | 29 | public QueryMethodHandler(CouchDbCrudRepository crudRepository, Object customRepository, 30 | ViewParamsMerger viewParamsMerger) { 31 | super(); 32 | this.crudRepository = crudRepository; 33 | this.customRepository = customRepository; 34 | this.viewParamsMerger = viewParamsMerger; 35 | } 36 | 37 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 38 | 39 | // delegate to the custom repository? 40 | if (customRepository != null) { 41 | Method underlyingMethod = ReflectionUtils.findMethod(customRepository.getClass(), method.getName(), 42 | method.getParameterTypes()); 43 | 44 | if (underlyingMethod != null) { 45 | try { 46 | return underlyingMethod.invoke(customRepository, args); 47 | } catch (InvocationTargetException e) { 48 | throw e.getCause(); // we want to throw the original exception 49 | } 50 | } 51 | } 52 | 53 | // delegate to the underlying CRUD repository? 54 | Method underlyingMethod = ReflectionUtils.findMethod(crudRepository.getClass(), method.getName(), 55 | method.getParameterTypes()); 56 | if (underlyingMethod != null) { 57 | try { 58 | return underlyingMethod.invoke(crudRepository, args); 59 | } catch (InvocationTargetException e) { 60 | throw e.getCause(); // we want to throw the original exception 61 | } 62 | } 63 | 64 | // find by view 65 | ViewParams viewParams = new ViewParams(); 66 | viewParamsMerger.mergeViewParams(viewParams, method, args); 67 | return crudRepository.find(viewParams); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/internal/ViewParamsMerger.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.internal; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.springframework.util.ReflectionUtils; 9 | 10 | import com.github.rwitzel.couchrepository.api.ViewParams; 11 | import com.thoughtworks.paranamer.AdaptiveParanamer; 12 | import com.thoughtworks.paranamer.BytecodeReadingParanamer; 13 | import com.thoughtworks.paranamer.CachingParanamer; 14 | import com.thoughtworks.paranamer.DefaultParanamer; 15 | import com.thoughtworks.paranamer.Paranamer; 16 | 17 | /** 18 | * Merges the arguments of a method call with the parameters of a {@link ViewParams} object. 19 | * 20 | * @author rwitzel 21 | */ 22 | public class ViewParamsMerger { 23 | 24 | protected Paranamer paranamer; 25 | 26 | public ViewParamsMerger() { 27 | this(new CachingParanamer(new AdaptiveParanamer( 28 | /* new Java8Paranamer(), new AnnotationParanamer(), */new DefaultParanamer(), 29 | new BytecodeReadingParanamer()))); 30 | } 31 | 32 | public ViewParamsMerger(Paranamer paranamer) { 33 | super(); 34 | this.paranamer = paranamer; 35 | } 36 | 37 | public void mergeViewParams(ViewParams resultingParams, Method method, Object[] args) { 38 | 39 | mergeViewParams(resultingParams, method.getName(), paranamer.lookupParameterNames(method), args); 40 | 41 | } 42 | 43 | public void mergeViewParams(ViewParams resultingParams, String methodName, String[] parameterNames, Object[] args) { 44 | 45 | ViewParams passedParams = null; 46 | 47 | // iterate through arguments 48 | for (int index = 0; index < parameterNames.length; index++) { 49 | String parameterName = parameterNames[index]; 50 | Object arg = args[index]; 51 | 52 | Field field = ReflectionUtils.findField(ViewParams.class, parameterName); 53 | if (field != null) { 54 | // -> parameter for ViewParams 55 | ReflectionUtils.makeAccessible(field); 56 | ReflectionUtils.setField(field, resultingParams, arg); 57 | } else if (parameterName.equals("viewParams") || parameterName.equals("params")) { 58 | // -> a ViewParam we have to merge into the resulting ViewParams later 59 | passedParams = (ViewParams) arg; 60 | } else { 61 | throw new RuntimeException("no parameter mapping defined for parameter with name " + parameterName 62 | + ", and value " + arg); 63 | } 64 | 65 | } 66 | 67 | // copy passed parameter to created parameters but do not override existing values 68 | if (passedParams != null) { 69 | Field[] fields = ViewParams.class.getDeclaredFields(); 70 | for (Field field : fields) { 71 | ReflectionUtils.makeAccessible(field); 72 | Object passedValue = ReflectionUtils.getField(field, passedParams); 73 | Object resultingValue = ReflectionUtils.getField(field, resultingParams); 74 | if (resultingValue == null && passedValue != null) { 75 | ReflectionUtils.setField(field, resultingParams, passedValue); 76 | } 77 | } 78 | } 79 | 80 | // set view name if not set yet 81 | if (resultingParams.getView() == null) { 82 | resultingParams.setView(methodName); 83 | } 84 | 85 | checkParams(resultingParams); 86 | } 87 | 88 | protected void checkParams(ViewParams viewParams) { 89 | 90 | List validationErrors = new ArrayList(); 91 | 92 | if (Boolean.TRUE.equals(viewParams.getIncludeDocs()) && viewParams.getDocumentType() == null) { 93 | validationErrors.add("includeDocs is true but the document type is null"); 94 | } 95 | if ((Boolean.FALSE.equals(viewParams.getIncludeDocs()) || viewParams.getIncludeDocs() == null) 96 | && viewParams.getDocumentType() != null) { 97 | validationErrors.add("includeDocs is false but the document type is not null"); 98 | } 99 | 100 | // null should be allowed (reduce=true) TODO LightCouch feature request! -> pull request is sent 101 | if (viewParams.getKeyType() == null) { 102 | validationErrors.add("keyType is null"); 103 | } 104 | if (viewParams.getValueType() == null) { 105 | validationErrors.add("valueType is null"); 106 | } 107 | 108 | if (validationErrors.size() > 0) { 109 | handleValidationErrors(validationErrors, viewParams); 110 | } 111 | } 112 | 113 | protected void handleValidationErrors(List validationErrors, ViewParams viewParams) { 114 | 115 | StringBuilder sb = new StringBuilder(viewParams.toString() + "\n"); 116 | for (String validationError : validationErrors) { 117 | sb.append(validationError + "\n"); 118 | } 119 | 120 | throw new IllegalArgumentException(sb.toString()); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/lightcouch/LightCouchCrudRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.lightcouch; 2 | 3 | import static com.github.rwitzel.couchrepository.internal.AdapterUtils.toList; 4 | import static com.github.rwitzel.couchrepository.internal.AdapterUtils.transformViewResult; 5 | 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.lightcouch.CouchDbClient; 15 | import org.lightcouch.NoDocumentException; 16 | import org.lightcouch.Response; 17 | import org.lightcouch.View; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.util.Assert; 21 | 22 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepository; 23 | import com.github.rwitzel.couchrepository.api.EntityInformation; 24 | import com.github.rwitzel.couchrepository.api.ViewParams; 25 | import com.github.rwitzel.couchrepository.api.ViewResult; 26 | import com.github.rwitzel.couchrepository.api.ViewResultRow; 27 | import com.github.rwitzel.couchrepository.api.exceptions.BulkOperationError; 28 | import com.github.rwitzel.couchrepository.api.exceptions.BulkOperationException; 29 | import com.github.rwitzel.couchrepository.support.GenericEntityInformation; 30 | 31 | /** 32 | * This implementation of {@link CouchDbCrudRepository} uses LightCouch's {@link CouchDbClient}. 33 | *

34 | * This implementation requires a special view in a design document. EXAMPLE: 35 | "views" : { 36 | "by_id" : { 37 | "map" : "function(doc) { if(doc.type == '...') {emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", 38 | "reduce" : "_count" 39 | } 40 | } 41 | 42 | *

43 | * Take care when using {@link #deleteAll()} because it loads the IDs and revisions of all documents of the 44 | * abovementioned view at once, then deletes the documents. 45 | * 46 | * @author rwitzel 47 | */ 48 | public class LightCouchCrudRepository implements CouchDbCrudRepository { 49 | 50 | protected Logger logger = LoggerFactory.getLogger(LightCouchCrudRepository.class); 51 | 52 | protected LightCouchViewConfigurer viewBuilder = new LightCouchViewConfigurer(); 53 | 54 | protected Class type; 55 | 56 | protected EntityInformation ei; 57 | 58 | protected boolean allOrNothing; 59 | 60 | protected CouchDbClient couchDbClient; 61 | 62 | @SuppressWarnings("unchecked") 63 | public LightCouchCrudRepository(Class type, CouchDbClient couchDbClient) { 64 | this(type, false, couchDbClient, new GenericEntityInformation(type, (Class) String.class)); 65 | } 66 | 67 | public LightCouchCrudRepository(Class type, boolean allOrNothing, CouchDbClient couchDbClient, 68 | EntityInformation ei) { 69 | super(); 70 | this.type = type; 71 | this.ei = ei; 72 | this.allOrNothing = allOrNothing; 73 | this.couchDbClient = couchDbClient; 74 | } 75 | 76 | public S save(S entity) { 77 | 78 | Assert.notNull(entity, "The given entity must not be null."); 79 | 80 | Response response; 81 | if (ei.isNew(entity)) { 82 | response = couchDbClient.save(entity); 83 | } else { 84 | response = couchDbClient.update(entity); 85 | } 86 | 87 | handleResponse(response, entity); 88 | 89 | return entity; 90 | } 91 | 92 | public Iterable saveAll(Iterable entities) { 93 | 94 | Assert.notNull(entities, "The given list of entities must not be null."); 95 | 96 | executeBulk(toList(entities), true); 97 | 98 | return entities; 99 | } 100 | 101 | public Optional findById(ID id) { 102 | 103 | Assert.notNull(id, "The given ID must not be null."); 104 | 105 | try { 106 | return Optional.of(couchDbClient.find(type, ei.toCouchId(id))); 107 | } catch (NoDocumentException e) { 108 | logger.debug("document with ID " + id + " not found", e); 109 | return Optional.ofNullable(null); 110 | } 111 | } 112 | 113 | public boolean existsById(ID id) { 114 | 115 | Assert.notNull(id, "The given ID must not be null."); 116 | 117 | return couchDbClient.contains(ei.toCouchId(id)); 118 | } 119 | 120 | public Iterable findAll() { 121 | return couchDbClient.view(designDocument() + "by_id").reduce(false).includeDocs(true).query(type); 122 | } 123 | 124 | public Iterable findAllById(Iterable ids) { 125 | 126 | Assert.notNull(ids, "The given list of IDs must not be null."); 127 | 128 | return couchDbClient.view(designDocument() + "by_id").keys(ei.toCouchIds(ids)).reduce(false).includeDocs(true) 129 | .query(type); 130 | } 131 | 132 | public long count() { 133 | @SuppressWarnings("rawtypes") 134 | List results = couchDbClient.view(designDocument() + "by_id").reduce(true).query(Map.class); 135 | if (results.size() == 0) { 136 | return 0; 137 | } else { 138 | return ((Number) results.get(0).get("value")).longValue(); 139 | } 140 | } 141 | 142 | @SuppressWarnings("rawtypes") 143 | public void deleteById(ID id) { 144 | 145 | Assert.notNull(id, "The given ID must not be null."); 146 | 147 | View view = couchDbClient.view(designDocument() + "by_id"); 148 | List results = view.key(ei.toCouchId(id)).reduce(false).includeDocs(false).query(Map.class); 149 | if (results.size() != 0) { 150 | Map result = (Map) results.get(0).get("value"); 151 | Response response = couchDbClient.remove((String) result.get("_id"), (String) result.get("_rev")); 152 | handleResponse(response, null); 153 | } 154 | } 155 | 156 | public void delete(T entity) { 157 | 158 | Response response = couchDbClient.remove(entity); 159 | 160 | handleResponse(response, null); 161 | } 162 | 163 | /** 164 | * @param response the response 165 | * @param entity Null if the entity shall not be updated 166 | */ 167 | protected void handleResponse(Response response, T entity) { 168 | if (response.getError() != null) { 169 | throw new RuntimeException(String.format("error: %s, reason: %s, entity id: %s, rev: %s", 170 | response.getError(), response.getReason(), response.getId(), response.getRev())); 171 | } else if (entity != null) { 172 | if (ei.isNew(entity)) { 173 | ei.setId(entity, response.getId()); 174 | } 175 | ei.setRev(entity, response.getRev()); 176 | } 177 | } 178 | 179 | @SuppressWarnings("rawtypes") 180 | public void deleteAll(Iterable entities) { 181 | 182 | Assert.notNull(entities, "The given list of entities must not be null."); 183 | 184 | List collection = new ArrayList(); 185 | for (T entity : entities) { 186 | collection.add(createBulkDeleteDocument(ei.getCouchId(entity), ei.getRev(entity))); 187 | } 188 | 189 | executeBulk(collection, false); 190 | } 191 | 192 | protected Map createBulkDeleteDocument(String id, String revision) { 193 | Map map = new HashMap(3); 194 | map.put("_id", id); 195 | map.put("_rev", revision); 196 | map.put("_deleted", true); 197 | return map; 198 | } 199 | 200 | @SuppressWarnings("unchecked") 201 | protected void executeBulk(List list, boolean updateEntities) { 202 | List responses = couchDbClient.bulk(list, allOrNothing); 203 | 204 | // handle errors 205 | List errors = new ArrayList(); 206 | List messages = new ArrayList(); 207 | int index = 0; 208 | for (Response response : responses) { 209 | if (response.getError() != null) { 210 | errors.add(new BulkOperationError(response.getId(), response.getRev(), response.getError(), response 211 | .getReason())); 212 | messages.add(response.toString()); 213 | } else if (updateEntities) { 214 | T entity = (T) list.get(index); 215 | if (ei.isNew(entity)) { 216 | ei.setId(entity, response.getId()); 217 | } 218 | ei.setRev(entity, response.getRev()); 219 | } 220 | index++; 221 | } 222 | if (errors.size() > 0) { 223 | throw new BulkOperationException(StringUtils.join(messages, ","), errors); 224 | } else { 225 | logger.debug("All documents have been processed."); 226 | } 227 | } 228 | 229 | @SuppressWarnings({ "rawtypes", "unchecked" }) 230 | public void deleteAll() { 231 | 232 | View view = couchDbClient.view(designDocument() + "by_id"); 233 | List results = view.reduce(false).includeDocs(false).query(Map.class); 234 | List deleteDocuments = new ArrayList(); 235 | for (Map result : results) { 236 | Map deleteDocument = (Map) result.get("value"); 237 | deleteDocument.put("_deleted", true); 238 | deleteDocuments.add(deleteDocument); 239 | } 240 | 241 | executeBulk(deleteDocuments, false); 242 | } 243 | 244 | @SuppressWarnings({ "rawtypes" }) 245 | public R find(ViewParams viewParams) { 246 | 247 | String designDocument = viewParams.getDesignDocument() != null ? viewParams.getDesignDocument() + "/" 248 | : designDocument(); 249 | View view = couchDbClient.view(designDocument + viewParams.getView()); 250 | viewBuilder.configure(view, viewParams); 251 | 252 | ViewResult viewResult; 253 | try { 254 | org.lightcouch.ViewResult lightCouchViewResult = view.queryView(viewParams.getKeyType(), 255 | viewParams.getValueType(), viewParams.getDocumentType()); 256 | viewResult = toViewResult(lightCouchViewResult); 257 | } catch (NoDocumentException e) { 258 | logger.debug("no documents found for view parameters " + viewParams, e); 259 | // best effort -> TODO LightCouch feature request -> pull request sent 260 | viewResult = new ViewResult(); 261 | } 262 | 263 | return transformViewResult(viewResult, viewParams.getReturnType()); 264 | } 265 | 266 | /** 267 | * Transforms a LightCouch-specific view result to CouchRepository's view result. 268 | * 269 | * @param viewResult the original view result 270 | * @return Returns the transformed view result. 271 | */ 272 | @SuppressWarnings({ "rawtypes", "unchecked" }) 273 | protected ViewResult toViewResult(org.lightcouch.ViewResult viewResult) { 274 | ViewResult result = new ViewResult(); 275 | result.setOffset(result.getOffset()); 276 | result.setTotalRows(viewResult.getTotalRows()); 277 | result.setUpdateSeq(viewResult.getUpdateSeq()); 278 | 279 | List rows = viewResult.getRows(); 280 | List resultRows = new ArrayList(); 281 | for (org.lightcouch.ViewResult.Rows row : rows) { 282 | ViewResultRow resultRow = new ViewResultRow(); 283 | resultRow.setId(row.getId()); 284 | resultRow.setDoc(row.getDoc()); 285 | resultRow.setKey(row.getKey()); 286 | resultRow.setValue(row.getValue()); 287 | resultRows.add(resultRow); 288 | } 289 | 290 | result.setRows(resultRows); 291 | 292 | return result; 293 | } 294 | 295 | /** 296 | * @return Returns the name of the design document to use. This is the ID of the design document without the 297 | * preceding "_design/". EXAMPLE: "Product". 298 | */ 299 | protected String designDocument() { 300 | return type.getSimpleName() + "/"; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/lightcouch/LightCouchViewConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.lightcouch; 2 | 3 | import org.lightcouch.View; 4 | 5 | import com.github.rwitzel.couchrepository.api.ViewParams; 6 | import com.github.rwitzel.couchrepository.api.exceptions.UnsupportedViewParameterException; 7 | 8 | /** 9 | * This internal class assists in configuring a {@link View}. 10 | * 11 | * @author rwitzel 12 | */ 13 | public class LightCouchViewConfigurer { 14 | 15 | /** 16 | * Configures LightCouch's {@link View} with the given {@link ViewParams}. 17 | * 18 | * @param view the LightCouch object to be configured 19 | * @param params the parameters that shall be used 20 | */ 21 | public void configure(View view, ViewParams params) { 22 | if (params.getAttachments() != null) { 23 | throw new UnsupportedViewParameterException("attachments"); 24 | } 25 | if (params.getAttEncodingInfo() != null) { 26 | throw new UnsupportedViewParameterException("att_encoding_info"); 27 | } 28 | if (params.getConflicts() != null) { 29 | throw new UnsupportedViewParameterException("conflicts"); 30 | } 31 | if (params.getDescending() != null) { 32 | view.descending(params.getDescending()); 33 | } 34 | if (params.getGroup() != null) { 35 | view.group(params.getGroup()); 36 | } 37 | if (params.getIncludeDocs() != null) { 38 | view.includeDocs(params.getIncludeDocs()); 39 | } 40 | if (params.getInclusiveEnd() != null) { 41 | view.inclusiveEnd(params.getInclusiveEnd()); 42 | } 43 | if (params.getReduce() != null) { 44 | view.reduce(params.getReduce()); 45 | } 46 | if (params.getUpdateSeq() != null) { 47 | view.updateSeq(params.getUpdateSeq()); 48 | } 49 | if (params.getEndKey() != null) { 50 | view.endKey(params.getEndKey()); 51 | } 52 | if (params.getEndKeyDocId() != null) { 53 | view.endKeyDocId(params.getEndKeyDocId()); 54 | } 55 | if (params.getGroupLevel() != null) { 56 | view.groupLevel(params.getGroupLevel()); 57 | } 58 | if (params.getKey() != null) { 59 | view.key(params.getKey()); 60 | } 61 | if (params.getLimit() != null) { 62 | view.limit(params.getLimit()); 63 | } 64 | if (params.getSkip() != null) { 65 | view.skip(params.getSkip()); 66 | } 67 | if (params.getStale() != null) { 68 | view.stale(params.getStale()); 69 | } 70 | if (params.getStartKey() != null) { 71 | view.startKey(params.getStartKey()); 72 | } 73 | if (params.getStartKeyDocId() != null) { 74 | view.startKeyDocId(params.getStartKeyDocId()); 75 | } 76 | if (params.getView() != null) { 77 | view.skip(params.getSkip()); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/support/DocumentLoader.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.support; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | 8 | import org.yaml.snakeyaml.Yaml; 9 | 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepository; 12 | 13 | /** 14 | * Loads a document into the database. Useful for the initialization of a database. 15 | * 16 | * @author rwitzel 17 | */ 18 | public class DocumentLoader { 19 | 20 | @SuppressWarnings("rawtypes") 21 | private CouchDbCrudRepository repository; 22 | 23 | /** 24 | * @param repository 25 | * A repository for {@link Map maps}. 26 | */ 27 | @SuppressWarnings("rawtypes") 28 | public DocumentLoader(CouchDbCrudRepository repository) { 29 | super(); 30 | this.repository = repository; 31 | } 32 | 33 | /** 34 | * Loads the given JSON document into the database. Overrides an existing document. 35 | * 36 | * @param documentContent 37 | * the JSON content of a document 38 | */ 39 | public void loadJson(InputStream documentContent) { 40 | save(parseJson(documentContent)); 41 | } 42 | 43 | /** 44 | * Loads the given YAML document into the database. Overrides an existing document. 45 | * 46 | * @param documentContent 47 | * the YAML content of a document 48 | */ 49 | public void loadYaml(InputStream documentContent) { 50 | save(parseYaml(documentContent)); 51 | } 52 | 53 | public void save(Map document) { 54 | 55 | String documentId = (String) document.get("_id"); 56 | 57 | if (repository.existsById(documentId)) { 58 | @SuppressWarnings("rawtypes") 59 | Optional documentInDb = repository.findById(documentId); 60 | document.put("_rev", documentInDb.get().get("_rev")); 61 | } 62 | 63 | repository.save(document); 64 | } 65 | 66 | @SuppressWarnings("unchecked") 67 | public Map parseJson(InputStream documentContent) { 68 | try { 69 | return (Map) new ObjectMapper().readValue(documentContent, Map.class); 70 | } catch (IOException e) { 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | public Map parseYaml(InputStream documentContent) { 77 | return (Map) new Yaml().load(documentContent); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/support/GenericEntityInformation.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.support; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.Field; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.springframework.data.domain.Persistable; 10 | import org.springframework.util.ReflectionUtils; 11 | 12 | import com.github.rwitzel.couchrepository.api.EntityInformation; 13 | 14 | /** 15 | * This {@link EntityInformation} is only for entities with ID of type String. 16 | *

17 | * It looks up the ID and the revision in the properties "_id", "id" resp. "_rev", "rev", "revision". 18 | * 19 | * @author rwitzel 20 | */ 21 | public class GenericEntityInformation implements EntityInformation { 22 | 23 | protected Class type; 24 | 25 | protected Class idType; 26 | 27 | public GenericEntityInformation(Class type, Class idType) { 28 | super(); 29 | this.type = type; 30 | this.idType = idType; 31 | } 32 | 33 | @Override 34 | public String toCouchId(ID id) { 35 | if (id == null) { 36 | return null; 37 | } else if (id instanceof CharSequence) { 38 | return ((CharSequence) id).toString(); 39 | } else { 40 | throw new RuntimeException("unsupported type. Should you use another implementation of " 41 | + EntityInformation.class + "?"); 42 | } 43 | } 44 | 45 | @Override 46 | public List toCouchIds(Iterable iter) { 47 | List list = new ArrayList(); 48 | for (ID item : iter) { 49 | list.add(toCouchId(item)); 50 | } 51 | return list; 52 | } 53 | 54 | 55 | @SuppressWarnings("unchecked") 56 | protected ID toId(String couchId) { 57 | if (couchId == null) { 58 | return null; 59 | } else if (idType == String.class) { 60 | return (ID) couchId; 61 | } else { 62 | throw new RuntimeException("unsupported type. Should you use another implementation of " 63 | + EntityInformation.class + "?"); 64 | } 65 | } 66 | 67 | @Override 68 | public String getCouchId(T entity) { 69 | return (String) getPropertyValue(entity, false, "_id", "id"); 70 | } 71 | 72 | @Override 73 | public String getRev(T entity) { 74 | return (String) getPropertyValue(entity, false, "_rev", "rev", "revision"); 75 | } 76 | 77 | @Override 78 | public void setId(T entity, String couchId) { 79 | setPropertyValue(entity, toId(couchId), "_id", "_id", "id"); 80 | } 81 | 82 | @Override 83 | public void setRev(T entity, String rev) { 84 | setPropertyValue(entity, rev, "_rev", "_rev", "rev", "revision"); 85 | } 86 | 87 | @SuppressWarnings("rawtypes") 88 | public boolean isNew(T entity) { 89 | if (entity instanceof Persistable) { 90 | return ((Persistable) entity).isNew(); 91 | } else { 92 | return getRev(entity) == null; 93 | } 94 | } 95 | 96 | @SuppressWarnings("rawtypes") 97 | protected Object getPropertyValue(T entity, boolean exceptionOnMissingProperty, String... properties) { 98 | 99 | for (String property : properties) { 100 | 101 | if (entity instanceof Map) { 102 | Map map = (Map) entity; 103 | if (map.containsKey(property)) { 104 | return map.get(property); 105 | } 106 | } 107 | Field field = ReflectionUtils.findField(type, property); 108 | if (field != null) { 109 | ReflectionUtils.makeAccessible(field); 110 | return ReflectionUtils.getField(field, entity); 111 | } 112 | 113 | } 114 | 115 | if (exceptionOnMissingProperty) { 116 | throw new RuntimeException("value for document properties " + properties + " not found"); 117 | } 118 | else { 119 | return null; 120 | } 121 | } 122 | 123 | @SuppressWarnings({ "rawtypes", "unchecked" }) 124 | protected void setPropertyValue(T entity, Object newValue, String defaultPropertyInMap, String... properties) { 125 | 126 | for (String property : properties) { 127 | 128 | if (entity instanceof Map) { 129 | Map map = (Map) entity; 130 | if (map.containsKey(property)) { 131 | map.put(property, newValue); 132 | return; 133 | } 134 | } 135 | Field field = ReflectionUtils.findField(type, property); 136 | if (field != null) { 137 | ReflectionUtils.makeAccessible(field); 138 | ReflectionUtils.setField(field, entity, newValue); 139 | return; 140 | } 141 | 142 | } 143 | 144 | if (defaultPropertyInMap != null) { 145 | Map map = (Map) entity; 146 | map.put(defaultPropertyInMap, newValue); 147 | } 148 | else{ 149 | throw new RuntimeException("value for document properties " + properties + " not found"); 150 | } 151 | 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/github/rwitzel/couchrepository/support/SimpleEntityInformation.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.support; 2 | 3 | import com.github.rwitzel.couchrepository.api.EntityInformation; 4 | 5 | /** 6 | * This {@link EntityInformation} is only for entities with ID of type String. 7 | *

8 | * It looks up the ID and the revision in the {@link #propId} resp. {@link #propRev}. 9 | * 10 | * @author rwitzel 11 | * 12 | * @param the type of the handled entities 13 | */ 14 | public class SimpleEntityInformation extends GenericEntityInformation{ 15 | 16 | private String propId; 17 | 18 | private String propRev; 19 | 20 | public SimpleEntityInformation(Class type, String propId, String propRev) { 21 | super(type, String.class); 22 | this.propId = propId; 23 | this.propRev = propRev; 24 | } 25 | 26 | @Override 27 | public String toCouchId(String id) { 28 | return id; 29 | } 30 | 31 | @Override 32 | public String getCouchId(T entity) { 33 | return (String) getPropertyValue(entity, true, propId); 34 | } 35 | 36 | @Override 37 | public String getRev(T entity) { 38 | return (String) getPropertyValue(entity, true, propRev); 39 | } 40 | 41 | @Override 42 | public void setId(T entity, String couchId) { 43 | setPropertyValue(entity, couchId, null, propId); 44 | } 45 | 46 | @Override 47 | public void setRev(T entity, String rev) { 48 | setPropertyValue(entity, rev, null, propRev); 49 | } 50 | 51 | @Override 52 | public boolean isNew(T entity) { 53 | return getRev(entity) == null; 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/AbstractAutomaticImplementationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.junit.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | 12 | import com.github.rwitzel.couchrepository.internal.QueryMethodHandler; 13 | import com.github.rwitzel.couchrepository.model.Comment; 14 | 15 | /** 16 | * Tests interfaces that must be automatically implemented by {@link QueryMethodHandler}. 17 | * 18 | * @author rwitzel 19 | */ 20 | public abstract class AbstractAutomaticImplementationTest extends AbstractCouchdbCrudRepositoryTest { 21 | 22 | @Autowired 23 | private ProductRepository productRepo; 24 | 25 | @Test 26 | public void testDelegateToFindByViewParams() throws Exception { 27 | 28 | deleteProductRepoAndCreateSomeProducts(); 29 | 30 | ViewParams viewParams = new ViewParams(); 31 | viewParams.setKeyType(String[].class); 32 | viewParams.setReduce(false); 33 | ViewResult comments = productRepo.findByComment(new Object[] { "authorB", "textB" }, null, viewParams, 34 | HashMap.class); 35 | assertEquals(1, comments.getRows().size()); 36 | Map value = comments.getRows().get(0).getValue(); 37 | assertEquals("textB", value.get("text")); 38 | } 39 | 40 | @Test 41 | public void testDelegateToFindByViewParams_ReturnValueType() throws Exception { 42 | 43 | deleteProductRepoAndCreateSomeProducts(); 44 | 45 | ViewParams viewParams = new ViewParams(); 46 | viewParams.setKeyType(String[].class); 47 | viewParams.setValueType(Comment.class); 48 | viewParams.setReturnType("value"); 49 | viewParams.setReduce(false); 50 | List comments = productRepo.findByComment(new Object[] { "authorB", "textB" }, viewParams); 51 | assertEquals(1, comments.size()); 52 | Comment value = comments.get(0); 53 | assertEquals("authorB", value.getAuthor()); 54 | assertEquals("textB", value.getText()); 55 | } 56 | 57 | @Test 58 | public void testDelegateToFindByViewParams_emptyViewResult() throws Exception { 59 | 60 | productRepo.deleteAll(); 61 | 62 | ViewParams viewParams = new ViewParams(); 63 | viewParams.setKeyType(String[].class); 64 | viewParams.setValueType(Comment.class); 65 | viewParams.setReturnType("value"); 66 | viewParams.setReduce(false); 67 | List comments = productRepo.findByComment(new Object[] { "authorB", "textB" }, viewParams); 68 | assertEquals(0, comments.size()); 69 | } 70 | 71 | @Test 72 | public void testDelegateToUnderlyingMethod() throws Exception { 73 | 74 | deleteProductRepoAndCreateSomeProducts(); 75 | 76 | assertEquals("p1", productRepo.findById("p1").get().getId()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/AbstractCouchdbCrudRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | 12 | import org.junit.Test; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | import com.github.rwitzel.couchrepository.api.viewresult.ProductSummary; 17 | import com.github.rwitzel.couchrepository.model.Product; 18 | 19 | /** 20 | * Tests implementations of {@link CouchDbCrudRepository}. 21 | * 22 | * @author rwitzel 23 | */ 24 | public abstract class AbstractCouchdbCrudRepositoryTest extends AbstractCrudRepositoryTest { 25 | 26 | @Autowired 27 | private CouchDbCrudRepository productRepo; 28 | 29 | private Product p1 = readDocument("../documents/p1_allAttributesSet.js", Product.class); 30 | 31 | private Product p2 = newProduct("Oak table 1922", "Lumberjack1 Inc."); 32 | 33 | private Product p3 = newProduct("Oak table 1923", "Lumberjack1 Inc."); 34 | 35 | private Product p4 = newProduct("Oak table 1924", "Lumberjack Inc."); 36 | 37 | private ViewParams params = new ViewParams(); 38 | 39 | protected void deleteProductRepoAndCreateSomeProducts() { 40 | 41 | productRepo.deleteAll(); 42 | 43 | productRepo.save(p1); 44 | productRepo.save(p2); 45 | productRepo.save(p3); 46 | productRepo.save(p4); 47 | 48 | params.setView("by_manufacturerId"); 49 | params.setReduce(false); 50 | params.setKeyType(String.class); 51 | params.setValueType(ProductSummary.class); 52 | } 53 | 54 | private T readDocument(String pathToResource, Class type) { 55 | InputStream documentContent = getClass().getResourceAsStream(pathToResource); 56 | try { 57 | return new ObjectMapper().readValue(documentContent, type); 58 | } catch (IOException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | @Test 64 | public void testCompleteResult() throws Exception { 65 | 66 | deleteProductRepoAndCreateSomeProducts(); 67 | 68 | params.setKey("Lumberjack Association"); 69 | 70 | ViewResult viewResults = productRepo.find(params); 71 | assertEquals(1, viewResults.getRows().size()); 72 | ProductSummary summary = viewResults.getRows().get(0).getValue(); 73 | assertEquals(p1.getId(), summary.getFacts().getDocId()); 74 | assertEquals(p1.getComments(), summary.getFacts().getComments()); 75 | assertEquals(p1.getIsoProductCode(), summary.getFacts().getIsoProductCode()); 76 | assertEquals(p1.getLastModification(), summary.getFacts().getLastModification()); 77 | assertEquals(p1.getNumBuyers(), summary.getFacts().getNumBuyers()); 78 | assertEquals(p1.getPrice().doubleValue(), summary.getFacts().getPrice().doubleValue(), 0.0001); 79 | assertEquals(p1.getRating(), summary.getFacts().getRating()); 80 | assertEquals(p1.getRevision(), summary.getFacts().getRevision()); 81 | assertEquals(p1.getTags(), summary.getFacts().getTags()); 82 | assertEquals(p1.getText(), summary.getFacts().getText()); 83 | assertEquals(p1.getWeight(), summary.getFacts().getWeight(), 0.00001); 84 | } 85 | 86 | @Test 87 | public void testSetKey() throws Exception { 88 | 89 | deleteProductRepoAndCreateSomeProducts(); 90 | 91 | params.setKey("Lumberjack1 Inc."); 92 | 93 | ViewResult summaries = productRepo.find(params); 94 | assertEquals(2, summaries.getRows().size()); 95 | 96 | List docIds = toDocIds(summaries.getRows()); 97 | List expectedDocsIds = Arrays.asList(p2.getId(), p3.getId()); 98 | assertEquals(new HashSet(expectedDocsIds), new HashSet(docIds)); 99 | } 100 | 101 | private List toDocIds(List results) { 102 | List docIds = new ArrayList(); 103 | for (ViewResultRow result : results) { 104 | docIds.add(result.getId()); 105 | } 106 | return docIds; 107 | } 108 | 109 | @Test 110 | public void testSetReduce() throws Exception { 111 | 112 | deleteProductRepoAndCreateSomeProducts(); 113 | 114 | params.setView("by_manufacturerId"); 115 | params.setReduce(true); 116 | params.setKeyType(String.class); // any 117 | params.setValueType(Integer.class); 118 | 119 | ViewResult summaries = productRepo.find(params); 120 | assertEquals(4, (int) summaries.getRows().get(0).getValue()); 121 | 122 | } 123 | 124 | @Test 125 | public void testSetReturnValueToKey() throws Exception { 126 | 127 | deleteProductRepoAndCreateSomeProducts(); 128 | 129 | params.setKey("Lumberjack1 Inc."); 130 | params.setReturnType("key"); 131 | 132 | List keys = productRepo.find(params); 133 | assertEquals(2, keys.size()); 134 | assertEquals("Lumberjack1 Inc.", keys.get(0)); 135 | assertEquals("Lumberjack1 Inc.", keys.get(1)); 136 | } 137 | 138 | @Test 139 | public void testSetReturnValueToId() throws Exception { 140 | 141 | deleteProductRepoAndCreateSomeProducts(); 142 | 143 | params.setKey("Lumberjack1 Inc."); 144 | params.setReturnType("id"); 145 | 146 | List docIds = productRepo.find(params); 147 | assertEquals(2, docIds.size()); 148 | 149 | List expectedDocsIds = Arrays.asList(p2.getId(), p3.getId()); 150 | assertEquals(new HashSet(expectedDocsIds), new HashSet(docIds)); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/AbstractCrudRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import static com.github.rwitzel.couchrepository.internal.AdapterUtils.toList; 4 | import static com.googlecode.catchexception.CatchException.catchException; 5 | import static com.googlecode.catchexception.CatchException.caughtException; 6 | import static java.util.Arrays.asList; 7 | import static java.util.Collections.singleton; 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertFalse; 10 | import static org.junit.Assert.assertNotEquals; 11 | import static org.junit.Assert.assertNotNull; 12 | import static org.junit.Assert.assertNull; 13 | import static org.junit.Assert.assertTrue; 14 | 15 | import java.math.BigDecimal; 16 | import java.math.MathContext; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.Set; 21 | 22 | import org.joda.time.DateTime; 23 | import org.junit.Test; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.data.repository.CrudRepository; 26 | import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 27 | 28 | import com.github.rwitzel.couchrepository.api.exceptions.BulkOperationException; 29 | import com.github.rwitzel.couchrepository.model.BaseDocument; 30 | import com.github.rwitzel.couchrepository.model.Comment; 31 | import com.github.rwitzel.couchrepository.model.Manufacturer; 32 | import com.github.rwitzel.couchrepository.model.Product; 33 | import com.github.rwitzel.couchrepository.model.ProductRating; 34 | 35 | /** 36 | * Tests implementations of {@link CrudRepository}. 37 | * 38 | * @author rwitzel 39 | */ 40 | public abstract class AbstractCrudRepositoryTest extends AbstractJUnit4SpringContextTests { 41 | 42 | @Autowired 43 | private CrudRepository productRepo; 44 | 45 | @Autowired 46 | private CrudRepository manufacturerRepo; 47 | 48 | @Test 49 | public void testSaveAndFindOneAndDeletebyEntityAndExists() throws Exception { 50 | 51 | // given 52 | Product newProduct = newProduct("Oak table 1922", "Lumberjack Inc."); 53 | 54 | // when 55 | Optional existingProduct = productRepo.findById(newProduct.getId()); 56 | if (existingProduct.isPresent()) { 57 | productRepo.delete(existingProduct.get()); 58 | } 59 | 60 | // then 61 | assertFalse(productRepo.findById(newProduct.getId()).isPresent()); 62 | 63 | // when 64 | productRepo.save(newProduct); 65 | 66 | // then 67 | assertTrue(productRepo.existsById(newProduct.getId())); 68 | Optional oFoundProduct = productRepo.findById(newProduct.getId()); 69 | assertTrue(oFoundProduct.isPresent()); 70 | Product foundProduct = oFoundProduct.get(); 71 | assertEquals(newProduct.getComments(), foundProduct.getComments()); 72 | assertEquals(newProduct.isHidden(), foundProduct.isHidden()); 73 | assertEquals(newProduct.getId(), foundProduct.getId()); 74 | assertEquals(newProduct.getIsoProductCode(), foundProduct.getIsoProductCode()); 75 | assertEquals(newProduct.getLastModification(), foundProduct.getLastModification()); 76 | assertEquals(newProduct.getManufacturerId(), foundProduct.getManufacturerId()); 77 | assertEquals(newProduct.getNumBuyers(), foundProduct.getNumBuyers()); 78 | assertEquals(newProduct.getPrice().doubleValue(), foundProduct.getPrice().doubleValue(), 0.001); 79 | assertEquals(newProduct.getRating(), foundProduct.getRating()); 80 | assertEquals(newProduct.getTags(), foundProduct.getTags()); 81 | assertEquals(newProduct.getText(), foundProduct.getText()); 82 | assertEquals(newProduct.getWeight(), foundProduct.getWeight(), 0.0001); 83 | 84 | // when 85 | productRepo.delete(foundProduct); 86 | 87 | // then 88 | assertFalse(productRepo.existsById(newProduct.getId())); 89 | assertFalse(productRepo.findById(foundProduct.getId()).isPresent()); 90 | } 91 | 92 | @Test 93 | public void testSaveIterableAndFindIterableAndDeleteIterable() throws Exception { 94 | 95 | productRepo.deleteAll(); 96 | 97 | // given 98 | 99 | Product newProduct1 = newProduct("Oak table 1720", "Lumberjack Inc."); 100 | Product newProduct2 = newProduct("Oak table 1721", "Lumberjack Inc."); 101 | Product newProduct3 = newProduct("Oak table 1722", "Lumberjack Inc."); 102 | productRepo.saveAll(asList(newProduct1, newProduct2, newProduct3)); 103 | 104 | // when 105 | List productIds = asList(newProduct1.getId(), newProduct2.getId()); 106 | List foundProducts = toList(productRepo.findAllById(productIds)); 107 | 108 | // then 109 | assertEqualsIdSet(productIds, foundProducts); 110 | 111 | // when 112 | productRepo.deleteAll(asList(foundProducts.get(0), foundProducts.get(1))); 113 | 114 | // then 115 | assertEqualsIdSet(singleton(newProduct3.getId()), toList(productRepo.findAll())); 116 | } 117 | 118 | @Test 119 | public void testSaveIterable_ErrorOnBulkOperation() throws Exception { 120 | 121 | productRepo.deleteAll(); 122 | 123 | // given 124 | 125 | Product newProduct1 = newProduct("Oak table 1720", "Lumberjack Inc."); 126 | Product newProduct2 = newProduct("Oak table 1721", "Lumberjack Inc."); 127 | Product newProduct3 = newProduct("Oak table 1722", "Lumberjack Inc."); 128 | productRepo.saveAll(asList(newProduct1, newProduct2, newProduct3)); 129 | 130 | String oldRevision2 = newProduct2.getRevision(); 131 | String oldRevision3 = newProduct3.getRevision(); 132 | newProduct2.setHidden(!newProduct2.isHidden()); 133 | newProduct3.setHidden(!newProduct3.isHidden()); 134 | 135 | productRepo.saveAll(asList(newProduct1, newProduct2, newProduct3)); 136 | 137 | // when 138 | newProduct2.setRevision(oldRevision2); 139 | newProduct3.setRevision(oldRevision3); 140 | catchException(productRepo).saveAll(asList(newProduct1, newProduct2, newProduct3)); 141 | assertTrue(caughtException() instanceof BulkOperationException ); 142 | BulkOperationException exception = caughtException(); 143 | assertEquals(2, exception.getErrors().size()); 144 | assertEquals(2, exception.getErrors().size()); 145 | } 146 | 147 | @Test 148 | public void testFindAllAndDeleteAllAndCount() throws Exception { 149 | 150 | // when 151 | productRepo.deleteAll(); 152 | manufacturerRepo.deleteAll(); 153 | 154 | // then 155 | assertEquals(0, productRepo.count()); 156 | assertEquals(0, manufacturerRepo.count()); 157 | 158 | // when 159 | Manufacturer newManufacturer = newManufacturer("Lumberjack Inc."); 160 | manufacturerRepo.save(newManufacturer); 161 | 162 | Product newProduct1 = newProduct("Oak table 1822", newManufacturer.getId()); 163 | Product newProduct2 = newProduct("Oak table 1823", newManufacturer.getId()); 164 | productRepo.saveAll(asList(newProduct1, newProduct2)); 165 | 166 | // then 167 | assertEquals(2, productRepo.count()); 168 | assertEquals(1, manufacturerRepo.count()); 169 | 170 | // when 171 | List foundManufacturers = toList(manufacturerRepo.findAll()); 172 | 173 | // then 174 | assertEquals(1, foundManufacturers.size()); 175 | assertEquals(newManufacturer.getId(), foundManufacturers.get(0).getId()); 176 | 177 | // when 178 | List foundProducts = toList(productRepo.findAll()); 179 | 180 | // then 181 | assertEqualsIdSet(asList(newProduct1.getId(), newProduct2.getId()), foundProducts); 182 | 183 | // when 184 | productRepo.deleteAll(); 185 | manufacturerRepo.deleteAll(); 186 | 187 | // then 188 | assertEquals(0, productRepo.count()); 189 | assertEquals(0, manufacturerRepo.count()); 190 | assertEquals(0, toList(manufacturerRepo.findAll()).size()); 191 | assertEquals(0, toList(productRepo.findAll()).size()); 192 | } 193 | 194 | @Test 195 | public void testDeleteById() throws Exception { 196 | productRepo.deleteAll(); 197 | 198 | // given 199 | Product newProduct = newProduct("Oak table 1822", "Lumberjack Inc."); 200 | productRepo.save(newProduct); 201 | 202 | assertTrue(productRepo.existsById(newProduct.getId())); 203 | 204 | // when 205 | productRepo.deleteById(newProduct.getId()); 206 | 207 | // then 208 | assertFalse(productRepo.existsById(newProduct.getId())); 209 | } 210 | 211 | private Manufacturer newManufacturer(String manufacturerId) { 212 | Manufacturer manufacturer = new Manufacturer(); 213 | 214 | manufacturer.setAddress("Berlin, Germany"); 215 | manufacturer.setCommercialRegisterCode(manufacturerId); 216 | manufacturer.setId(manufacturerId); 217 | 218 | return manufacturer; 219 | } 220 | 221 | protected Product newProduct(String productId, String manufacturerId) { 222 | Product product = new Product(); 223 | 224 | product.setComments(asList(newComment(1), newComment(2))); 225 | product.setHidden(true); 226 | product.setId(productId); 227 | product.setIsoProductCode(123); 228 | product.setLastModification(DateTime.now().minusDays(1).toDate()); 229 | product.setManufacturerId(manufacturerId); 230 | product.setNumBuyers(null); // we want to set a property with value null 231 | product.setPrice(new BigDecimal(1.23000000, new MathContext(10))); 232 | product.setRating(ProductRating.FourStars); 233 | product.setTags(asList("wooden", "blue")); 234 | product.setText("Vintage\nBargain"); 235 | product.setWeight(34.00); 236 | 237 | return product; 238 | } 239 | 240 | private Comment newComment(int id) { 241 | Comment comment = new Comment(); 242 | comment.setAuthor("author" + id); 243 | comment.setText("text" + id); 244 | return comment; 245 | } 246 | 247 | private void assertEqualsIdSet(Iterable ids, Iterable docs) { 248 | 249 | Set idsFromDocs = new HashSet(); 250 | for (BaseDocument doc : toList(docs)) { 251 | idsFromDocs.add(doc.getId()); 252 | } 253 | 254 | ids = new HashSet(toList(ids)); 255 | 256 | assertEquals(ids, idsFromDocs); 257 | } 258 | 259 | /** 260 | * Only for manual testing. 261 | * 262 | * @throws Exception 263 | */ 264 | @Test 265 | //@Ignore 266 | public void createSomeTestDocuments() throws Exception { 267 | 268 | productRepo.deleteAll(); 269 | manufacturerRepo.deleteAll(); 270 | 271 | Manufacturer newManufacturer = newManufacturer("Lumberjack Inc."); 272 | manufacturerRepo.save(newManufacturer); 273 | 274 | Product newProduct1 = newProduct("Oak table 1822", newManufacturer.getId()); 275 | Product newProduct2 = newProduct("Oak table 1823", newManufacturer.getId()); 276 | productRepo.saveAll(asList(newProduct1, newProduct2)); 277 | } 278 | 279 | @Test 280 | public void testSave_NewDocumentVsUpdatedDocument() throws Exception { 281 | 282 | // given 283 | Product newProduct = newProduct(null, "Lumberjack Inc."); 284 | 285 | // when 286 | productRepo.save(newProduct); 287 | 288 | // then 289 | assertNotNull(newProduct.getId()); 290 | assertNotNull(newProduct.getRevision()); 291 | 292 | // when 293 | String oldRevision = newProduct.getRevision(); 294 | newProduct.setHidden(!newProduct.isHidden()); 295 | productRepo.save(newProduct); 296 | 297 | // then 298 | assertNotEquals(oldRevision, newProduct.getRevision()); 299 | } 300 | 301 | 302 | } 303 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/AbstractCustomImplementationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import static com.googlecode.catchexception.CatchException.catchException; 4 | import static com.googlecode.catchexception.CatchException.caughtException; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.util.List; 9 | 10 | import org.junit.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | 13 | import com.github.rwitzel.couchrepository.model.Product; 14 | 15 | /** 16 | * Tests the integration of custom implementations of query methods. 17 | * 18 | * @author rwitzel 19 | */ 20 | public abstract class AbstractCustomImplementationTest extends AbstractAutomaticImplementationTest { 21 | 22 | @Autowired 23 | private ProductRepository productRepo; 24 | 25 | @Test 26 | public void testDelegateToCustomImplementation() throws Exception { 27 | 28 | deleteProductRepoAndCreateSomeProducts(); 29 | 30 | List products = productRepo.findByComment(new Object[] { "authorB", "textB" }, true); 31 | assertEquals(1, products.size()); 32 | Product value = products.get(0); 33 | assertEquals("p1", value.getId()); 34 | } 35 | 36 | 37 | @Test 38 | public void testDelegateToUnderlyingMethod_exceptionUnwrapped() throws Exception { 39 | 40 | catchException(productRepo).throwException(new Object[] { "authorB", "textB" }, true); 41 | assertTrue(caughtException() instanceof UnsupportedOperationException); 42 | assertEquals("this exception is thrown to test exception unwrapping", caughtException().getMessage()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/AbstractExoticTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import static java.util.Collections.singleton; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.util.Optional; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import com.github.rwitzel.couchrepository.model.Exotic; 15 | import com.github.rwitzel.couchrepository.model.ExoticId; 16 | 17 | /** 18 | * Tests entities with custom ID and revision attributes. 19 | * 20 | * @author rwitzel 21 | */ 22 | public abstract class AbstractExoticTest extends AbstractCustomImplementationTest { 23 | 24 | @Autowired 25 | CouchDbCrudRepository exoticRepository; 26 | 27 | @Before 28 | public void before() { 29 | exoticRepository.deleteAll(); 30 | } 31 | 32 | @Test 33 | public void testExoticSaveAndFindOneAndFindSomeDocumentWithId() { 34 | 35 | // given 36 | ExoticId id1 = new ExoticId(123, "abc", true); 37 | Exotic doc1 = new Exotic(id1); 38 | doc1.setData("test1"); 39 | 40 | ExoticId id2 = new ExoticId(123, "xyz", true); 41 | Exotic doc2 = new Exotic(id2); 42 | doc2.setData("test2"); 43 | 44 | // when 45 | exoticRepository.save(doc1); 46 | exoticRepository.save(doc2); 47 | 48 | // then 49 | Optional oFoundDoc = exoticRepository.findById(id1); 50 | assertTrue(oFoundDoc.isPresent()); 51 | assertEquals(doc1.getData(), oFoundDoc.get().getData()); 52 | 53 | // when 54 | oFoundDoc = exoticRepository.findById(new ExoticId(123, "def", true)); 55 | 56 | // then 57 | assertFalse(oFoundDoc.isPresent()); 58 | 59 | // when 60 | Iterable foundDocs = exoticRepository.findAllById(singleton(id1)); 61 | 62 | // then 63 | Exotic foundDoc = foundDocs.iterator().next(); 64 | assertEquals(doc1.getData(), foundDoc.getData()); 65 | } 66 | 67 | @Test 68 | public void testExoticSaveAndSaveAgainAndDelete() { 69 | 70 | // given 71 | ExoticId id1 = new ExoticId(123, "abc", true); 72 | Exotic doc1 = new Exotic(id1); 73 | doc1.setData("test1"); 74 | exoticRepository.save(doc1); 75 | 76 | // when 77 | doc1.setData("test1_changed"); 78 | exoticRepository.save(doc1); 79 | 80 | // then 81 | Optional foundDoc = exoticRepository.findById(id1); 82 | assertEquals(doc1.getData(), foundDoc.get().getData()); 83 | 84 | // when 85 | exoticRepository.delete(doc1); 86 | 87 | // then 88 | assertFalse(exoticRepository.findById(id1).isPresent()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Named; 6 | 7 | import com.github.rwitzel.couchrepository.model.Comment; 8 | import com.github.rwitzel.couchrepository.model.Product; 9 | 10 | public interface ProductRepository extends CouchDbCrudRepository, ProductRepositoryCustom { 11 | 12 | ViewResult findByComment(@Named("key") Object[] key, @Named("descending") Boolean descending, 13 | @Named("viewParams") ViewParams viewParams, @Named("valueType") Class valueType); 14 | 15 | ViewResult findByComment(); 16 | 17 | List findByComment(@Named("key") Object[] key, @Named("viewParams") ViewParams viewParams); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/ProductRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.util.List; 4 | 5 | import com.github.rwitzel.couchrepository.model.Product; 6 | 7 | public interface ProductRepositoryCustom { 8 | 9 | List findByComment(Object[] key, Boolean descending); 10 | 11 | List throwException(Object[] key, Boolean descending); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/ProductRepositoryCustomImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api; 2 | 3 | import java.util.List; 4 | 5 | import com.github.rwitzel.couchrepository.model.Product; 6 | 7 | /** 8 | * A repository with custom methods. 9 | * 10 | * @author rwitzel 11 | */ 12 | public class ProductRepositoryCustomImpl implements ProductRepositoryCustom { 13 | 14 | private CouchDbCrudRepository repository; 15 | 16 | public ProductRepositoryCustomImpl(CouchDbCrudRepository repository) { 17 | super(); 18 | this.repository = repository; 19 | } 20 | 21 | @Override 22 | public List findByComment(Object[] key, Boolean descending) { 23 | 24 | ViewParams params = new ViewParams(); 25 | params.setReduce(false); 26 | params.setIncludeDocs(true); 27 | params.setReturnType("doc"); 28 | params.setDocumentType(Product.class); 29 | params.setValueType(Object.class); 30 | params.setKeyType(Object.class); 31 | params.setView("findByComment"); 32 | 33 | params.setKey(key); 34 | params.setDescending(descending); 35 | 36 | return repository.find(params); 37 | } 38 | 39 | @Override 40 | public List throwException(Object[] key, Boolean descending) { 41 | throw new UnsupportedOperationException("this exception is thrown to test exception unwrapping"); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/viewresult/ProductFacts.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api.viewresult; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import com.github.rwitzel.couchrepository.model.Comment; 8 | import com.github.rwitzel.couchrepository.model.Product; 9 | import com.github.rwitzel.couchrepository.model.ProductRating; 10 | 11 | /** 12 | * This class duplicates the properties of a {@link Product} and assists in testing views. 13 | * 14 | * @author rwitzel 15 | */ 16 | public class ProductFacts { 17 | 18 | private String docId; 19 | 20 | private Date lastModification; 21 | 22 | private String text; 23 | 24 | private ProductRating rating; 25 | 26 | private boolean hidden; 27 | 28 | private Integer numBuyers; 29 | 30 | private double weight; 31 | 32 | private BigDecimal price; 33 | 34 | private List tags; 35 | 36 | private List comments; 37 | 38 | private int isoProductCode; 39 | 40 | private String revision; 41 | 42 | public Date getLastModification() { 43 | return lastModification; 44 | } 45 | 46 | public void setLastModification(Date lastModification) { 47 | this.lastModification = lastModification; 48 | } 49 | 50 | public String getText() { 51 | return text; 52 | } 53 | 54 | public void setText(String text) { 55 | this.text = text; 56 | } 57 | 58 | public ProductRating getRating() { 59 | return rating; 60 | } 61 | 62 | public void setRating(ProductRating rating) { 63 | this.rating = rating; 64 | } 65 | 66 | public boolean isHidden() { 67 | return hidden; 68 | } 69 | 70 | public void setHidden(boolean hidden) { 71 | this.hidden = hidden; 72 | } 73 | 74 | public Integer getNumBuyers() { 75 | return numBuyers; 76 | } 77 | 78 | public void setNumBuyers(Integer numBuyers) { 79 | this.numBuyers = numBuyers; 80 | } 81 | 82 | public double getWeight() { 83 | return weight; 84 | } 85 | 86 | public void setWeight(double weight) { 87 | this.weight = weight; 88 | } 89 | 90 | public BigDecimal getPrice() { 91 | return price; 92 | } 93 | 94 | public void setPrice(BigDecimal price) { 95 | this.price = price; 96 | } 97 | 98 | public List getTags() { 99 | return tags; 100 | } 101 | 102 | public void setTags(List tags) { 103 | this.tags = tags; 104 | } 105 | 106 | public List getComments() { 107 | return comments; 108 | } 109 | 110 | public void setComments(List comments) { 111 | this.comments = comments; 112 | } 113 | 114 | public int getIsoProductCode() { 115 | return isoProductCode; 116 | } 117 | 118 | public void setIsoProductCode(int isoProductCode) { 119 | this.isoProductCode = isoProductCode; 120 | } 121 | 122 | public String getDocId() { 123 | return docId; 124 | } 125 | 126 | public void setDocId(String docId) { 127 | this.docId = docId; 128 | } 129 | 130 | public String getRevision() { 131 | return revision; 132 | } 133 | 134 | public void setRevision(String revision) { 135 | this.revision = revision; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/api/viewresult/ProductSummary.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.api.viewresult; 2 | 3 | /** 4 | * This class assists in testing views. 5 | * 6 | * @author rwitzel 7 | */ 8 | public class ProductSummary { 9 | 10 | private ProductFacts facts; 11 | 12 | public ProductFacts getFacts() { 13 | return facts; 14 | } 15 | 16 | public void setFacts(ProductFacts facts) { 17 | this.facts = facts; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/ektorp/EktorpCrudRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.ektorp; 2 | 3 | import org.springframework.test.context.ContextConfiguration; 4 | 5 | import com.github.rwitzel.couchrepository.api.AbstractExoticTest; 6 | 7 | @ContextConfiguration(classes = { EktorpTestConfiguration.class }) 8 | public class EktorpCrudRepositoryTest extends AbstractExoticTest { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/ektorp/EktorpTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.ektorp; 2 | 3 | import java.util.Map; 4 | import java.util.Properties; 5 | 6 | import org.ektorp.CouchDbConnector; 7 | import org.ektorp.CouchDbInstance; 8 | import org.ektorp.http.HttpClient; 9 | import org.ektorp.impl.StdCouchDbConnector; 10 | import org.ektorp.impl.StdCouchDbInstance; 11 | import org.ektorp.spring.HttpClientFactoryBean; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Lazy; 15 | 16 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepositoryFactory; 17 | import com.github.rwitzel.couchrepository.api.ProductRepository; 18 | import com.github.rwitzel.couchrepository.api.ProductRepositoryCustom; 19 | import com.github.rwitzel.couchrepository.api.ProductRepositoryCustomImpl; 20 | import com.github.rwitzel.couchrepository.internal.ViewParamsMerger; 21 | import com.github.rwitzel.couchrepository.model.Exotic; 22 | import com.github.rwitzel.couchrepository.model.ExoticEntityInformation; 23 | import com.github.rwitzel.couchrepository.model.ExoticId; 24 | import com.github.rwitzel.couchrepository.model.Manufacturer; 25 | import com.github.rwitzel.couchrepository.model.Product; 26 | import com.github.rwitzel.couchrepository.support.DocumentLoader; 27 | import com.thoughtworks.paranamer.AnnotationParanamer; 28 | 29 | /** 30 | * This configuration uses the the database "/ektorp-integration-tests/", creates or updates the standard design 31 | * document including the views of the design document. 32 | * 33 | * @author rwitzel 34 | */ 35 | @Configuration 36 | public class EktorpTestConfiguration { 37 | 38 | private CouchDbCrudRepositoryFactory factory = new CouchDbCrudRepositoryFactory(new ViewParamsMerger( 39 | new AnnotationParanamer())); 40 | 41 | @Bean 42 | public StdCouchDbConnector connector() throws Exception { 43 | 44 | String url = "http://localhost:5984/"; 45 | String databaseName = "ektorp-integration-tests"; 46 | 47 | Properties properties = new Properties(); 48 | properties.setProperty("autoUpdateViewOnChange", "true"); 49 | 50 | HttpClientFactoryBean factory = new HttpClientFactoryBean(); 51 | factory.setUrl(url); 52 | factory.setProperties(properties); 53 | factory.afterPropertiesSet(); 54 | HttpClient client = factory.getObject(); 55 | 56 | CouchDbInstance dbInstance = new StdCouchDbInstance(client); 57 | return new StdCouchDbConnector(databaseName, dbInstance); 58 | } 59 | 60 | @SuppressWarnings({ "unchecked", "rawtypes" }) 61 | @Bean 62 | @Lazy(false) 63 | public String createDatabaseAndUpdateDesignDocuments(CouchDbConnector db) { 64 | 65 | db.createDatabaseIfNotExists(); 66 | 67 | DocumentLoader loader = new DocumentLoader(new EktorpCrudRepository(Map.class, db)); 68 | 69 | loader.loadYaml(getClass().getResourceAsStream("../Product.yaml")); 70 | loader.loadJson(getClass().getResourceAsStream("../Manufacturer.json")); 71 | loader.loadJson(getClass().getResourceAsStream("../Exotic.json")); 72 | 73 | return "OK"; // anything 74 | } 75 | 76 | @SuppressWarnings({ "unchecked", "rawtypes" }) 77 | @Bean 78 | public ProductRepository productRepository(CouchDbConnector db) { 79 | EktorpCrudRepository underlyingRepository = new EktorpCrudRepository(Product.class, db); 80 | ProductRepositoryCustom customRepository = new ProductRepositoryCustomImpl(underlyingRepository); 81 | return factory.createRepository(underlyingRepository, customRepository, ProductRepository.class); 82 | } 83 | 84 | @Bean 85 | public EktorpCrudRepository manufacturerRepository(CouchDbConnector db) { 86 | return new EktorpCrudRepository(Manufacturer.class, db); 87 | } 88 | 89 | @Bean 90 | public EktorpCrudRepository exoticRepository(CouchDbConnector db) { 91 | return new EktorpCrudRepository(Exotic.class, db, new ExoticEntityInformation()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/internal/ViewParamsMergerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.internal; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertNotNull; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.Map; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.springframework.util.ReflectionUtils; 13 | 14 | import com.github.rwitzel.couchrepository.api.ProductRepository; 15 | import com.github.rwitzel.couchrepository.api.ViewParams; 16 | import com.thoughtworks.paranamer.AnnotationParanamer; 17 | 18 | /** 19 | * Tests {@link ViewParamsMerger}. 20 | * 21 | * @author rwitzel 22 | */ 23 | public class ViewParamsMergerTest { 24 | 25 | private ViewParamsMerger merger = new ViewParamsMerger(new AnnotationParanamer()); 26 | 27 | private ViewParams passedParams = new ViewParams(); 28 | 29 | private ViewParams resultingParams = new ViewParams(); 30 | 31 | @Before 32 | public void before() { 33 | // prevent validation errors -> set any non-null value 34 | resultingParams.setValueType(RuntimeException.class); 35 | resultingParams.setKeyType(RuntimeException.class); 36 | } 37 | 38 | @Test 39 | public void testCreateViewParams() throws Exception { 40 | 41 | Method method = ReflectionUtils.findMethod(ProductRepository.class, "findByComment", Object[].class, 42 | Boolean.class, ViewParams.class, Class.class); 43 | assertNotNull(method); 44 | 45 | passedParams.setKeyType(String.class); 46 | passedParams.setLimit(100); 47 | merger.mergeViewParams(resultingParams, method, new Object[] { 48 | new Object[] { "hemingway", "once upon a time" }, true, passedParams, Map.class }); 49 | assertEquals("findByComment", resultingParams.getView()); 50 | assertEquals(true, resultingParams.getDescending()); 51 | assertEquals(100, (int) resultingParams.getLimit()); 52 | assertArrayEquals(new Object[] { "hemingway", "once upon a time" }, (Object[]) resultingParams.getKey()); 53 | assertEquals("findByComment", resultingParams.getView()); 54 | assertEquals(null, resultingParams.getDesignDocument()); 55 | } 56 | 57 | @Test 58 | public void testCreateViewParams_precedence_resultingParametersOverPassedParameters() throws Exception { 59 | 60 | // given 61 | resultingParams.setKeyType(Integer.class); 62 | passedParams.setKeyType(String.class); 63 | 64 | // when 65 | merger.mergeViewParams(resultingParams, "findByComment", new String[] { "viewParams"}, new Object[] { passedParams} ); 66 | 67 | // then 68 | assertEquals(Integer.class, resultingParams.getKeyType()); // the passed parameter wins 69 | } 70 | 71 | @Test 72 | public void testCreateViewParams_precedence_methodParametersOverPassedParameters() throws Exception { 73 | 74 | // given 75 | passedParams.setKeyType(String.class); 76 | 77 | // when 78 | merger.mergeViewParams(resultingParams, "findByComment", new String[] { "keyType", "viewParams"}, new Object[] { Integer.class, passedParams} ); 79 | 80 | // then 81 | assertEquals(Integer.class, resultingParams.getKeyType()); // the method parameter wins 82 | } 83 | 84 | @Test 85 | public void testCreateViewParams_precedence_passedParameterOverMethodName() throws Exception { 86 | 87 | // given 88 | passedParams.setView("findByComment2"); 89 | 90 | // when 91 | merger.mergeViewParams(resultingParams, "findByComment", new String[] { "viewParams"}, new Object[] { passedParams} ); 92 | 93 | // then 94 | assertEquals("findByComment2", resultingParams.getView()); // the passed parameter wins 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/lightcouch/LightCouchCrudRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.lightcouch; 2 | 3 | import org.springframework.test.context.ContextConfiguration; 4 | 5 | import com.github.rwitzel.couchrepository.api.AbstractExoticTest; 6 | 7 | @ContextConfiguration(classes = { LightCouchTestConfiguration.class }) 8 | public class LightCouchCrudRepositoryTest extends AbstractExoticTest { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/lightcouch/LightCouchTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.lightcouch; 2 | 3 | import java.util.Map; 4 | 5 | import org.lightcouch.CouchDbClient; 6 | import org.lightcouch.CouchDbProperties; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Lazy; 10 | 11 | import com.github.rwitzel.couchrepository.api.CouchDbCrudRepositoryFactory; 12 | import com.github.rwitzel.couchrepository.api.ProductRepository; 13 | import com.github.rwitzel.couchrepository.api.ProductRepositoryCustom; 14 | import com.github.rwitzel.couchrepository.api.ProductRepositoryCustomImpl; 15 | import com.github.rwitzel.couchrepository.internal.ViewParamsMerger; 16 | import com.github.rwitzel.couchrepository.model.Exotic; 17 | import com.github.rwitzel.couchrepository.model.ExoticEntityInformation; 18 | import com.github.rwitzel.couchrepository.model.ExoticId; 19 | import com.github.rwitzel.couchrepository.model.Manufacturer; 20 | import com.github.rwitzel.couchrepository.model.Product; 21 | import com.github.rwitzel.couchrepository.support.DocumentLoader; 22 | import com.google.gson.GsonBuilder; 23 | import com.thoughtworks.paranamer.AnnotationParanamer; 24 | 25 | @Configuration 26 | public class LightCouchTestConfiguration { 27 | 28 | private CouchDbCrudRepositoryFactory factory = new CouchDbCrudRepositoryFactory(new ViewParamsMerger( 29 | new AnnotationParanamer())); 30 | 31 | @Bean(destroyMethod = "shutdown") 32 | public CouchDbClient couchDbClient() { 33 | 34 | CouchDbProperties properties = new CouchDbProperties(); 35 | properties.setProtocol("http"); 36 | properties.setHost("localhost"); 37 | properties.setPort(5984); 38 | properties.setDbName("lightcouch-integration-tests"); 39 | 40 | CouchDbClient client = new CouchDbClient(properties); 41 | // we adjust the date format so that it is equal to the format used by Ektorp 42 | client.setGsonBuilder(new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); 43 | return client; 44 | } 45 | 46 | @SuppressWarnings({ "unchecked", "rawtypes" }) 47 | @Bean 48 | @Lazy(false) 49 | public String createDatabaseAndUpdateDesignDocuments(CouchDbClient dbClient) { 50 | 51 | dbClient.context().createDB("lightcouch-integration-tests"); 52 | 53 | DocumentLoader loader = new DocumentLoader(new LightCouchCrudRepository(Map.class, dbClient)); 54 | 55 | loader.loadYaml(getClass().getResourceAsStream("../Product.yaml")); 56 | loader.loadJson(getClass().getResourceAsStream("../Manufacturer.json")); 57 | loader.loadJson(getClass().getResourceAsStream("../Exotic.json")); 58 | 59 | return "OK"; // anything 60 | } 61 | 62 | @SuppressWarnings({ "unchecked", "rawtypes" }) 63 | @Bean 64 | public ProductRepository productRepository(CouchDbClient couchDbClient) { 65 | LightCouchCrudRepository underlyingRepository = new LightCouchCrudRepository(Product.class, couchDbClient); 66 | ProductRepositoryCustom customRepository = new ProductRepositoryCustomImpl(underlyingRepository); 67 | return factory.createRepository(underlyingRepository, customRepository, ProductRepository.class); 68 | } 69 | 70 | @Bean 71 | public LightCouchCrudRepository manufacturerRepo(CouchDbClient couchDbClient) { 72 | return new LightCouchCrudRepository(Manufacturer.class, couchDbClient); 73 | } 74 | 75 | @Bean 76 | public LightCouchCrudRepository exoticRepository(CouchDbClient couchDbClient) { 77 | return new LightCouchCrudRepository(Exotic.class, false, couchDbClient, 78 | new ExoticEntityInformation()); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/BaseDocument.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | /** 9 | * @author rwitzel 10 | */ 11 | @JsonInclude(Include.NON_NULL) 12 | public class BaseDocument { 13 | 14 | @SerializedName("_id") 15 | private String id; 16 | 17 | @SerializedName("_rev") 18 | private String revision; 19 | 20 | @JsonProperty("_id") 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | @JsonProperty("_id") 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | @JsonProperty("_rev") 31 | public String getRevision() { 32 | return revision; 33 | } 34 | 35 | @JsonProperty("_rev") 36 | public void setRevision(String revision) { 37 | this.revision = revision; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/Comment.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | public class Comment { 4 | 5 | private String author; 6 | 7 | private String text; 8 | 9 | public String getAuthor() { 10 | return author; 11 | } 12 | 13 | public void setAuthor(String author) { 14 | this.author = author; 15 | } 16 | 17 | public String getText() { 18 | return text; 19 | } 20 | 21 | public void setText(String text) { 22 | this.text = text; 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | final int prime = 31; 28 | int result = 1; 29 | result = prime * result + ((author == null) ? 0 : author.hashCode()); 30 | result = prime * result + ((text == null) ? 0 : text.hashCode()); 31 | return result; 32 | } 33 | 34 | /** 35 | * Generated by Eclipse. 36 | */ 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) 40 | return true; 41 | if (obj == null) 42 | return false; 43 | if (getClass() != obj.getClass()) 44 | return false; 45 | Comment other = (Comment) obj; 46 | if (author == null) { 47 | if (other.author != null) 48 | return false; 49 | } else if (!author.equals(other.author)) 50 | return false; 51 | if (text == null) { 52 | if (other.text != null) 53 | return false; 54 | } else if (!text.equals(other.text)) 55 | return false; 56 | return true; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/Exotic.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | /** 9 | * The primary key of this document is a composed object: {@link ExoticId}. 10 | * 11 | * @author rwitzel 12 | */ 13 | @JsonInclude(Include.NON_NULL) 14 | public class Exotic { 15 | 16 | /** 17 | * CouchDB-specific property that allows us to distinct exotic documents from other documents. 18 | */ 19 | @JsonProperty("type") 20 | private String type = "exotic"; 21 | 22 | /** 23 | * The ID for CouchDB. Computed from {@link #id}. 24 | */ 25 | @SerializedName("_id") 26 | @JsonProperty("_id") 27 | private String computedKey; 28 | 29 | /** 30 | * The domain-specific ID. 31 | */ 32 | @JsonProperty("internalId") 33 | private ExoticId id; 34 | 35 | /** 36 | * The revision property for CouchDB. 37 | */ 38 | @SerializedName("_rev") 39 | @JsonProperty("_rev") 40 | private String version; 41 | 42 | private String data; 43 | 44 | public Exotic() { 45 | super(); 46 | } 47 | 48 | public Exotic(ExoticId id) { 49 | super(); 50 | this.id = id; 51 | this.computedKey = id.toCouchId(); 52 | } 53 | 54 | public String getData() { 55 | return data; 56 | } 57 | 58 | public void setData(String data) { 59 | this.data = data; 60 | } 61 | 62 | public ExoticId getId() { 63 | return id; 64 | } 65 | 66 | public void setId(ExoticId id) { 67 | this.id = id; 68 | } 69 | 70 | public String getComputedKey() { 71 | return computedKey; 72 | } 73 | 74 | public void setComputedKey(String computedKey) { 75 | this.computedKey = computedKey; 76 | } 77 | 78 | public String getVersion() { 79 | return version; 80 | } 81 | 82 | public void setVersion(String version) { 83 | this.version = version; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/ExoticEntityInformation.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.github.rwitzel.couchrepository.api.EntityInformation; 7 | import com.github.rwitzel.couchrepository.ektorp.EktorpEntityInformation; 8 | 9 | /** 10 | * An {@link EktorpEntityInformation} for {@link Exotic} entities. 11 | * 12 | * @author rwitzel 13 | */ 14 | public class ExoticEntityInformation implements EntityInformation { 15 | 16 | @Override 17 | public String toCouchId(ExoticId id) { 18 | return id.toCouchId(); 19 | } 20 | 21 | @Override 22 | public List toCouchIds(Iterable iter) { 23 | List list = new ArrayList(); 24 | for (ExoticId item : iter) { 25 | list.add(toCouchId(item)); 26 | } 27 | return list; 28 | } 29 | 30 | @Override 31 | public String getCouchId(Exotic entity) { 32 | return entity.getComputedKey(); 33 | } 34 | 35 | @Override 36 | public String getRev(Exotic entity) { 37 | return entity.getVersion(); 38 | } 39 | 40 | @Override 41 | public void setId(Exotic entity, String couchId) { 42 | entity.setComputedKey(couchId); 43 | entity.setId(new ExoticId(couchId)); 44 | } 45 | 46 | @Override 47 | public void setRev(Exotic entity, String rev) { 48 | entity.setVersion(rev); 49 | } 50 | 51 | @Override 52 | public boolean isNew(Exotic entity) { 53 | return getRev(entity) == null; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/ExoticId.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * The primary key for {@link Exotic}. 7 | * 8 | * @author rwitzel 9 | */ 10 | public class ExoticId implements Serializable { 11 | 12 | private static final long serialVersionUID = -2640126433764396121L; 13 | 14 | private Integer idPart1; 15 | 16 | private String idPart2; 17 | 18 | private Boolean idPart3; 19 | 20 | public ExoticId() { 21 | super(); 22 | } 23 | 24 | public ExoticId(String couchId) { 25 | super(); 26 | 27 | String[] parts = couchId.split("_"); 28 | this.idPart1 = Integer.parseInt(parts[0]); 29 | this.idPart2 = parts[1]; 30 | this.idPart3 = Boolean.parseBoolean(parts[2]); 31 | } 32 | 33 | public ExoticId(Integer idPart1, String idPart2, Boolean idPart3) { 34 | super(); 35 | this.idPart1 = idPart1; 36 | this.idPart2 = idPart2; 37 | this.idPart3 = idPart3; 38 | } 39 | 40 | public Integer getIdPart1() { 41 | return idPart1; 42 | } 43 | 44 | public String getIdPart2() { 45 | return idPart2; 46 | } 47 | 48 | public Boolean getIdPart3() { 49 | return idPart3; 50 | } 51 | 52 | public String toCouchId() { 53 | return toCouchId(idPart1, idPart2, idPart3); 54 | } 55 | 56 | public static String toCouchId(Integer idPart1, String idPart2, Boolean idPart3) { 57 | return idPart1 + "_" + idPart2 + "_" + idPart3; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/Manufacturer.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | 4 | /** 5 | * Represents a manufacturer description, not a manufacturer. 6 | * 7 | * @author rwitzel 8 | */ 9 | public class Manufacturer extends BaseDocument { 10 | 11 | private String address; 12 | 13 | private String commercialRegisterCode; 14 | 15 | public String getAddress() { 16 | return address; 17 | } 18 | 19 | public void setAddress(String address) { 20 | this.address = address; 21 | } 22 | 23 | public String getCommercialRegisterCode() { 24 | return commercialRegisterCode; 25 | } 26 | 27 | public void setCommercialRegisterCode(String commercialRegisterCode) { 28 | this.commercialRegisterCode = commercialRegisterCode; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.ArrayList; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | /** 9 | * Represents a product description, not a product. 10 | * 11 | * @author rwitzel 12 | */ 13 | public class Product extends BaseDocument { 14 | 15 | private Date lastModification; 16 | 17 | private String manufacturerId; 18 | 19 | private String text; 20 | 21 | private ProductRating rating; 22 | 23 | private boolean hidden; 24 | 25 | private Integer numBuyers; 26 | 27 | private double weight; 28 | 29 | private BigDecimal price; 30 | 31 | private List tags; 32 | 33 | private List comments; 34 | 35 | private int isoProductCode; 36 | 37 | public Date getLastModification() { 38 | return lastModification; 39 | } 40 | 41 | public void setLastModification(Date lastModification) { 42 | this.lastModification = lastModification; 43 | } 44 | 45 | public String getText() { 46 | return text; 47 | } 48 | 49 | public void setText(String text) { 50 | this.text = text; 51 | } 52 | 53 | public ProductRating getRating() { 54 | return rating; 55 | } 56 | 57 | public void setRating(ProductRating rating) { 58 | this.rating = rating; 59 | } 60 | 61 | public boolean isHidden() { 62 | return hidden; 63 | } 64 | 65 | public void setHidden(boolean hidden) { 66 | this.hidden = hidden; 67 | } 68 | 69 | public Integer getNumBuyers() { 70 | return numBuyers; 71 | } 72 | 73 | public void setNumBuyers(Integer numBuyers) { 74 | this.numBuyers = numBuyers; 75 | } 76 | 77 | public double getWeight() { 78 | return weight; 79 | } 80 | 81 | public void setWeight(double weight) { 82 | this.weight = weight; 83 | } 84 | 85 | public BigDecimal getPrice() { 86 | return price; 87 | } 88 | 89 | public void setPrice(BigDecimal price) { 90 | this.price = price; 91 | } 92 | 93 | public List getTags() { 94 | if (tags == null) { 95 | return new ArrayList(); 96 | } 97 | return tags; 98 | } 99 | 100 | public void setTags(List tags) { 101 | this.tags = tags; 102 | } 103 | 104 | public List getComments() { 105 | if (comments == null) { 106 | comments = new ArrayList(); 107 | } 108 | return comments; 109 | } 110 | 111 | public void setComments(List comments) { 112 | this.comments = comments; 113 | } 114 | 115 | public String getManufacturerId() { 116 | return manufacturerId; 117 | } 118 | 119 | public void setManufacturerId(String manufacturerId) { 120 | this.manufacturerId = manufacturerId; 121 | } 122 | 123 | public int getIsoProductCode() { 124 | return isoProductCode; 125 | } 126 | 127 | public void setIsoProductCode(int isoProductCode) { 128 | this.isoProductCode = isoProductCode; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/model/ProductRating.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.model; 2 | 3 | public enum ProductRating { 4 | 5 | FiveStars, FourStars, ThreeStars, TwoStars, OneStars 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/github/rwitzel/couchrepository/support/DocumentLoaderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rwitzel.couchrepository.support; 2 | 3 | import static org.hamcrest.Matchers.containsString; 4 | import static org.hamcrest.Matchers.endsWith; 5 | import static org.hamcrest.Matchers.startsWith; 6 | import static org.junit.Assert.assertEquals; 7 | import static org.junit.Assert.assertThat; 8 | 9 | import java.util.Map; 10 | 11 | import org.junit.Test; 12 | 13 | /** 14 | * Tests {@link DocumentLoader}. 15 | * 16 | * @author rwitzel 17 | */ 18 | public class DocumentLoaderTest { 19 | 20 | private DocumentLoader loader = new DocumentLoader(null); 21 | 22 | @Test 23 | public void testParseJson() throws Exception { 24 | Map doc = loader.parseJson(getClass().getResourceAsStream("../Product.json")); 25 | 26 | assertEquals("_design/Product", get(doc, "_id")); 27 | assertEquals("_count", get(doc, "views", "by_id", "reduce")); 28 | assertEquals("function(doc) { if(doc.isoProductCode && doc._id) " 29 | + "{emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", get(doc, "views", "by_id", "map")); 30 | } 31 | 32 | @Test 33 | public void testParseJsonWithYaml() throws Exception { 34 | Map doc = loader.parseYaml(getClass().getResourceAsStream("../Product.json")); 35 | 36 | assertEquals("_design/Product", get(doc, "_id")); 37 | assertEquals("_count", get(doc, "views", "by_id", "reduce")); 38 | assertEquals("function(doc) { if(doc.isoProductCode && doc._id) " 39 | + "{emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", get(doc, "views", "by_id", "map")); 40 | } 41 | 42 | @Test 43 | public void testParseYaml() throws Exception { 44 | Map doc = loader.parseYaml(getClass().getResourceAsStream("../Product.yaml")); 45 | 46 | assertEquals("_design/Product", get(doc, "_id")); 47 | assertEquals("_count", get(doc, "views", "by_id", "reduce")); 48 | String mapFunction = get(doc, "views", "by_id", "map").trim(); 49 | assertThat(mapFunction, startsWith("function(doc) {")); 50 | assertThat(mapFunction, endsWith("}")); 51 | assertThat(mapFunction, containsString("\n")); 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | private String get(Map map, String... path) { 56 | for (String element : path) { 57 | Object value = map.get(element); 58 | if (value instanceof Map) { 59 | map = (Map) value; 60 | } else { 61 | return (String) value; 62 | } 63 | } 64 | throw new RuntimeException("programming error"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/com/github/rwitzel/couchrepository/Exotic.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id" : "_design/Exotic", 3 | "language" : "javascript", 4 | "views" : { 5 | "by_id" : { 6 | "map" : "function(doc) { if(doc.type == 'exotic') { emit(doc._id, { _id : doc._id, _rev: doc._rev } ) } }", 7 | "reduce" : "_count" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/com/github/rwitzel/couchrepository/Manufacturer.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id" : "_design/Manufacturer", 3 | "language" : "javascript", 4 | "views" : { 5 | "by_id" : { 6 | "map" : "function(doc) { if(doc.commercialRegisterCode && doc._id) {emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", 7 | "reduce" : "_count" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/com/github/rwitzel/couchrepository/Product.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id" : "_design/Product", 3 | "language" : "javascript", 4 | "views" : { 5 | "by_id" : { 6 | "map" : "function(doc) { if(doc.isoProductCode && doc._id) {emit(doc._id, { _id : doc._id, _rev: doc._rev } )} }", 7 | "reduce" : "_count" 8 | }, 9 | "by_manufacturerId" : { 10 | "map" : "function(doc) { if(doc.isoProductCode && doc._id) {emit(doc.manufacturerId, { facts : { lastModification : doc.lastModification, text: doc.text, isoProductCode : doc.isoProductCode, rating : doc.rating, hidden : doc.hidden, weight : doc.weight, price : doc.price, tags : doc.tags, comments : doc.comments, revision: doc._rev, docId : doc._id } } ) } }", 11 | "reduce" : "_count" 12 | }, 13 | "findByComment" : { 14 | "map" : "function(doc) { if(doc.isoProductCode && doc._id) { for (index = 0; index < doc.comments.length; index++) { comment = doc.comments[index]; emit([ comment.author, comment.text], comment ); } } } ", 15 | "reduce" : "_count" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/test/resources/com/github/rwitzel/couchrepository/Product.yaml: -------------------------------------------------------------------------------- 1 | _id : _design/Product 2 | language : javascript 3 | views : 4 | by_id : 5 | map : | 6 | function(doc) { 7 | 8 | if ( doc.isoProductCode && doc._id ) { 9 | 10 | emit(doc._id, 11 | { 12 | _id : doc._id, 13 | _rev: doc._rev 14 | }); 15 | 16 | } 17 | 18 | } 19 | reduce : _count 20 | by_manufacturerId : 21 | map : | 22 | function(doc) { 23 | 24 | if ( doc.isoProductCode && doc._id) { 25 | 26 | emit(doc.manufacturerId, 27 | { 28 | facts : 29 | { 30 | lastModification : doc.lastModification, 31 | text: doc.text, 32 | isoProductCode : doc.isoProductCode, 33 | rating : doc.rating, 34 | hidden : doc.hidden, 35 | weight : doc.weight, 36 | price : doc.price, 37 | tags : doc.tags, 38 | comments : doc.comments, 39 | revision: doc._rev, 40 | docId : doc._id 41 | } 42 | }); 43 | } 44 | } 45 | reduce : _count 46 | findByComment : 47 | map : | 48 | function(doc) { 49 | 50 | if ( doc.isoProductCode && doc._id) { 51 | 52 | for (index = 0; index < doc.comments.length; index++) { 53 | 54 | comment = doc.comments[index]; 55 | 56 | emit([ comment.author, comment.text], comment ); 57 | } 58 | } 59 | } 60 | reduce : _count 61 | -------------------------------------------------------------------------------- /src/test/resources/com/github/rwitzel/couchrepository/documents/p1_allAttributesSet.js: -------------------------------------------------------------------------------- 1 | { 2 | "_id" : "p1", 3 | "lastModification" : 1414074189591, 4 | "manufacturerId" : "Lumberjack Association", 5 | "text" : "Oak table 1922, Vintage\nBargain", 6 | "rating" : "FourStars", 7 | "hidden" : true, 8 | "weight" : 34.0, 9 | "price" : 1.230000000, 10 | "tags" : [ "wooden", "blue" ], 11 | "comments" : [ { 12 | "author" : "authorA", 13 | "text" : "textA" 14 | }, { 15 | "author" : "authorB", 16 | "text" : "textB" 17 | } ], 18 | "isoProductCode" : 123 19 | } -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d %5p | %t | %-55logger{55} | %m | %n 8 | UTF-8 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------