├── .gitignore ├── .project ├── .rultor.yml ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.m2e.core.prefs ├── .travis.yml ├── LICENSE ├── README.md ├── db-conversion-api ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── conversion │ └── api │ ├── JsonDeserializer.java │ ├── JsonSerializationFactory.java │ ├── JsonSerializer.java │ └── package-info.java ├── db-conversion-jackson ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ └── db │ │ └── conversion │ │ └── jackson │ │ ├── JacksonConversionException.java │ │ ├── JacksonJsonDeserializer.java │ │ ├── JacksonJsonSerializer.java │ │ ├── JacksonSerializationFactory.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── conversion │ └── jackson │ ├── Animal.java │ ├── ConversionTest.java │ ├── JacksonConversionExceptionTest.java │ ├── JacksonJsonDeserializerTest.java │ ├── JacksonJsonSerializerTest.java │ └── JacksonSerializationFactoryTest.java ├── db-entity ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── entity │ ├── Document.java │ ├── Entity.java │ └── package-info.java ├── db-integration-test ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ ├── Application.java │ │ └── db │ │ └── integration │ │ └── test │ │ ├── NodeTestConfig.java │ │ ├── SpringElasticSearchTest.java │ │ ├── TestConversionConfig.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── integration │ └── test │ ├── ApplicationTest.java │ └── ElasticTest.java ├── db-repository-api ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── repository │ └── api │ ├── DatabaseRepository.java │ ├── DatabaseRepositoryFactory.java │ └── package-info.java ├── db-repository-elasticsearch ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ └── db │ │ └── repository │ │ └── elasticsearch │ │ ├── ElasticSearchRepository.java │ │ ├── ElasticSearchRepositoryFactory.java │ │ ├── EntityScroll.java │ │ └── package-info.java │ └── test │ ├── java │ └── com │ │ └── jeromeloisel │ │ └── db │ │ └── repository │ │ └── elasticsearch │ │ ├── ElasticSearchRepositoryFactoryTest.java │ │ ├── ElasticSearchRepositoryTest.java │ │ ├── EntityScrollTest.java │ │ ├── Person.java │ │ ├── RepositoryIntegrationTest.java │ │ └── TestRepositoryConfig.java │ └── resources │ └── datas.json ├── db-scroll-api ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ └── db │ │ └── scroll │ │ └── api │ │ ├── DatabaseScroll.java │ │ ├── DatabaseScrolling.java │ │ └── DatabaseScrollingFactory.java │ └── test │ └── java │ └── com │ └── jeromeloisel │ └── db │ └── scroll │ └── api │ ├── DatabaseScrollTest.java │ └── MockDatabaseScroll.java ├── db-scroll-elastic ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ └── database │ │ └── scroll │ │ └── elastic │ │ ├── BulkDelete.java │ │ ├── BulkIndex.java │ │ ├── CompoundScroll.java │ │ ├── ElasticScroll.java │ │ ├── ElasticScrollingFactory.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── jeromeloisel │ └── database │ └── scroll │ └── elastic │ ├── BulkDeleteEsTest.java │ ├── BulkDeleteTest.java │ ├── BulkIndexEsTest.java │ ├── BulkIndexTest.java │ ├── CompoundScrollTest.java │ ├── ElasticScrollTest.java │ ├── ElasticScrollingFactoryEsTest.java │ ├── ElasticScrollingFactoryTest.java │ └── Person.java ├── db-spring-elasticsearch-starter ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── jeromeloisel │ │ └── repository │ │ └── elasticsearch │ │ └── starter │ │ ├── ElasticSearchRepositoryFactoryAutoConfiguration.java │ │ ├── JacksonConversionAutoConfiguration.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── jeromeloisel │ ├── Application.java │ └── repository │ └── elasticsearch │ └── starter │ ├── TestConfig.java │ └── WiringTest.java ├── pom.xml ├── pubring.gpg.asc ├── secring.gpg.asc └── settings.xml.asc /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | *.iml 10 | .idea/ -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | elasticsearch-crud 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.m2e.core.maven2Builder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.m2e.core.maven2Nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.rultor.yml: -------------------------------------------------------------------------------- 1 | docker: 2 | image: "maven:3-jdk-8" 3 | decrypt: 4 | settings.xml: "repo/settings.xml.asc" 5 | pubring.gpg: "repo/pubring.gpg.asc" 6 | secring.gpg: "repo/secring.gpg.asc" 7 | release: 8 | script: | 9 | mvn versions:set "-DnewVersion=${tag}" 10 | git commit -am "${tag}" 11 | mvn clean deploy -Pelastic-crud --settings /home/r/settings.xml 12 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | after_success: 5 | - mvn clean test jacoco:report coveralls:report 6 | -------------------------------------------------------------------------------- /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 | [![Build Status](https://travis-ci.org/jloisel/elastic-crud.svg)](https://travis-ci.org/jloisel/elastic-crud) 2 | [![Dependency Status](https://www.versioneye.com/user/projects/568d2e269c1b98002b000030/badge.svg?style=flat)](https://www.versioneye.com/user/projects/568d2e269c1b98002b000030) 3 | [![Coverage Status](https://coveralls.io/repos/jloisel/elastic-crud/badge.svg?branch=master&service=github)](https://coveralls.io/github/jloisel/elastic-crud?branch=master) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jeromeloisel/db-spring-elasticsearch-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jeromeloisel/db-spring-elasticsearch-starter) 5 | [![Javadoc](https://javadoc-emblem.rhcloud.com/doc/com.jcabi/jcabi-email/badge.svg)](http://www.javadoc.io/doc/com.jeromeloisel/elastic-crud) 6 | 7 | ## Elasticsearch Simple CRUD Repository 8 | 9 | Easily perform Create / Read / Update / Delete operations on beans stored in Elasticsearch. [Spring Data Elasticsearch](https://github.com/spring-projects/spring-data-elasticsearch) lacks maintenance and is already a few Elasticsearch versions behind the latest version. 10 | 11 | This project powers our [JMeter Load Testing platform](https://octoperf.com). 12 | 13 | ### Versions 14 | 15 | The following table shows the correspondance between our versions and Elasticsearch versions: 16 | 17 | | Version | ElasticSearch Version | 18 | | ------------- |:---------------------:| 19 | | 1.1.x | 2.1.x | 20 | | 2.2.x | 2.2.x | 21 | | 2.3.x | 2.3.x | 22 | | 5.1.x | 5.1.x | 23 | | 5.6.x | 5.6.x | 24 | 25 | As of 2.2.x, the project is going to strictly follow the same versioning as [elasticsearch](https://github.com/elastic/elasticsearch). 26 | 27 | ### Spring 28 | 29 | Add the following Maven dependency to get started quickly with Spring: 30 | 31 | ```xml 32 | 33 | com.jeromeloisel 34 | db-spring-elasticsearch-starter 35 | 5.6.3 36 | 37 | ``` 38 | ### Vanilla Java 39 | 40 | To get started with Vanilla Java application, you need to add two dependencies: 41 | 42 | ```xml 43 | 44 | com.jeromeloisel 45 | db-conversion-jackson 46 | 5.6.3 47 | 48 | ``` 49 | This dependency provides the Jackson Json serialization mechanism. 50 | 51 | ```xml 52 | 53 | com.jeromeloisel 54 | db-repository-elasticsearch 55 | 5.6.3 56 | 57 | ``` 58 | 59 | This dependency provides the **ElasticSearchRepositoryFactory** to create **ElasticRepository**. 60 | 61 | ### Java Example 62 | 63 | Suppose we would like to persist the following Bean in Elasticsearch: 64 | 65 | ```java 66 | @Value 67 | @Builder 68 | @Document(indexName="datas", type="person") 69 | public class Person implements Entity { 70 | @Wither 71 | String id; 72 | String firstname; 73 | String lastname; 74 | 75 | @JsonCreator 76 | Person( 77 | @JsonProperty("id") final String id, 78 | @JsonProperty("firstname") final String firstname, 79 | @JsonProperty("lastname") final String lastname) { 80 | super(); 81 | this.id = id; 82 | this.firstname = checkNotNull(firstname); 83 | this.lastname = checkNotNull(lastname); 84 | } 85 | } 86 | ``` 87 | 88 | The following code shows how to use the CRUD repository: 89 | 90 | ```java 91 | @Autowired 92 | private ElasticSearchRepositoryFactory factory; 93 | 94 | public void method() { 95 | final ElasticRepository repository = factory.create(Person.class); 96 | 97 | final Person person = Person.builder().id("").firstname("John").lastname("Smith").build(); 98 | final Person withId = repository.save(person); 99 | 100 | // Find by id 101 | final Optional byId = repository.findOne(withId.getId()); 102 | assertTrue(repository.exists(byId)); 103 | 104 | // Search by firstname (with "not_analyzed" string mapping) 105 | final TermQueryBuilder term = new TermQueryBuilder("firstname", PERSON.getFirstname()); 106 | final List found = repository.search(term); 107 | assertTrue(found.contains(byId)); 108 | 109 | // Delete from Elasticsearch definitively 110 | repository.delete(withId.getId()); 111 | assertFalse(repository.exists(byId)); 112 | } 113 | ``` 114 | 115 | Also, scrolling through massive amount of results is made dead easy with the scrolling API: 116 | ```java 117 | @Autowired 118 | private DatabaseScrollingFactory factory; 119 | 120 | public void example() { 121 | // Incorporated bulk delete 122 | factory 123 | .newScroll("myIndex") 124 | .withQuery(new MatchAllQueryBuilder()) 125 | .scroll(factory.bulkDelete()); 126 | 127 | } 128 | ``` 129 | You simply have to implement the `DatabaseScroll` interface: 130 | 131 | ```java 132 | @FunctionalInterface 133 | public interface DatabaseScroll { 134 | 135 | default void onStartBatch() throws IOException { 136 | 137 | } 138 | 139 | void accept(SearchHit hit) throws IOException; 140 | 141 | default void onEndBatch() throws IOException { 142 | 143 | } 144 | } 145 | 146 | ``` 147 | 148 | ### Type mapping 149 | 150 | Beans stored in Elasticsearch must have **_source** field enabled: see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html. The following example Json shows how to enable _source field: 151 | 152 | ```json 153 | { 154 | "template": "datas", 155 | "settings": { 156 | "number_of_shards": 5, 157 | "number_of_replicas": 1, 158 | "index.refresh_interval": -1, 159 | }, 160 | "mappings": { 161 | "_default_": { 162 | "_all": { 163 | "enabled": false 164 | }, 165 | "_source": { 166 | "enabled": true 167 | } 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | ### Index refresh 174 | 175 | Every mutating query (insert, delete) performed on the index automatically refreshes it. I would recommend to disable index refresh as shows in the Json above. 176 | 177 | ### Json Serialization 178 | 179 | The Json serialization is configured to use [Jackson](https://github.com/FasterXML/jackson) by default. To use Jackson Json serialization, simply add Jackson as dependency: 180 | 181 | ```xml 182 | 183 | com.fasterxml.jackson.core 184 | jackson-databind 185 | ${jackson.version} 186 | 187 | ``` 188 | 189 | Replace **${jackson.version}** with the version you are using. 190 | 191 | If you intend to use your own Json serialization mechanism (like Gson), please provide an implementation for the **JsonSerializationFactory** interface. 192 | 193 | ### Elasticsearch Client 194 | 195 | An instance of the [Elasticsearch Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html) must be provided. 196 | -------------------------------------------------------------------------------- /db-conversion-api/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-conversion-api/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-conversion-api 10 | 11 | 12 | 13 | com.jeromeloisel 14 | db-entity 15 | ${project.version} 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /db-conversion-api/src/main/java/com/jeromeloisel/db/conversion/api/JsonDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.api; 2 | 3 | import java.util.function.Function; 4 | 5 | import com.jeromeloisel.db.entity.Entity; 6 | 7 | public interface JsonDeserializer extends Function { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /db-conversion-api/src/main/java/com/jeromeloisel/db/conversion/api/JsonSerializationFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.api; 2 | 3 | import com.jeromeloisel.db.entity.Entity; 4 | 5 | public interface JsonSerializationFactory { 6 | 7 | JsonSerializer serializer(Class clazz); 8 | 9 | JsonDeserializer deserializer(Class clazz); 10 | } 11 | -------------------------------------------------------------------------------- /db-conversion-api/src/main/java/com/jeromeloisel/db/conversion/api/JsonSerializer.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.api; 2 | 3 | import java.util.function.Function; 4 | 5 | import com.jeromeloisel.db.entity.Entity; 6 | 7 | public interface JsonSerializer extends Function { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /db-conversion-api/src/main/java/com/jeromeloisel/db/conversion/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts beans back and forth to/from Json. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.conversion.api; 8 | -------------------------------------------------------------------------------- /db-conversion-jackson/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-conversion-jackson/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-conversion-jackson 10 | 11 | 12 | 13 | com.jeromeloisel 14 | db-conversion-api 15 | ${project.version} 16 | 17 | 18 | com.jeromeloisel 19 | db-entity 20 | ${project.version} 21 | 22 | 23 | com.fasterxml.jackson.core 24 | jackson-databind 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/main/java/com/jeromeloisel/db/conversion/jackson/JacksonConversionException.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | class JacksonConversionException extends RuntimeException { 6 | private static final long serialVersionUID = -4917703394134871581L; 7 | 8 | JacksonConversionException(final String message, final Throwable e) { 9 | super(requireNonNull(message), requireNonNull(e)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/main/java/com/jeromeloisel/db/conversion/jackson/JacksonJsonDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static lombok.AccessLevel.PACKAGE; 4 | import static lombok.AccessLevel.PRIVATE; 5 | 6 | import java.io.IOException; 7 | 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 10 | import com.jeromeloisel.db.entity.Entity; 11 | 12 | import lombok.AllArgsConstructor; 13 | import lombok.NonNull; 14 | import lombok.experimental.FieldDefaults; 15 | 16 | 17 | @AllArgsConstructor(access=PACKAGE) 18 | @FieldDefaults(level=PRIVATE, makeFinal=true) 19 | final class JacksonJsonDeserializer implements JsonDeserializer { 20 | @NonNull 21 | ObjectMapper mapper; 22 | @NonNull 23 | Class clazz; 24 | 25 | @Override 26 | public T apply(final String json) { 27 | try { 28 | return mapper.readerFor(clazz).readValue(json); 29 | } catch (final IOException e) { 30 | throw new JacksonConversionException("Could not deserialize json", e); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/main/java/com/jeromeloisel/db/conversion/jackson/JacksonJsonSerializer.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static lombok.AccessLevel.PACKAGE; 4 | import static lombok.AccessLevel.PRIVATE; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 9 | import com.jeromeloisel.db.entity.Entity; 10 | 11 | import lombok.AllArgsConstructor; 12 | import lombok.NonNull; 13 | import lombok.experimental.FieldDefaults; 14 | 15 | 16 | @AllArgsConstructor(access=PACKAGE) 17 | @FieldDefaults(level=PRIVATE, makeFinal=true) 18 | final class JacksonJsonSerializer implements JsonSerializer { 19 | @NonNull 20 | ObjectMapper mapper; 21 | 22 | @Override 23 | public String apply(final T t) { 24 | try { 25 | return mapper.writeValueAsString(t); 26 | } catch (final JsonProcessingException e) { 27 | throw new JacksonConversionException("Could not serialize entity=" + t.getClass(), e); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/main/java/com/jeromeloisel/db/conversion/jackson/JacksonSerializationFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static lombok.AccessLevel.PRIVATE; 4 | 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 7 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 8 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 9 | import com.jeromeloisel.db.entity.Entity; 10 | 11 | import lombok.AllArgsConstructor; 12 | import lombok.NonNull; 13 | import lombok.experimental.FieldDefaults; 14 | 15 | @FieldDefaults(level=PRIVATE, makeFinal=true) 16 | @AllArgsConstructor 17 | public final class JacksonSerializationFactory implements JsonSerializationFactory { 18 | @NonNull 19 | ObjectMapper mapper; 20 | 21 | @Override 22 | public JsonSerializer serializer(final Class clazz) { 23 | return new JacksonJsonSerializer<>(mapper); 24 | } 25 | 26 | @Override 27 | public JsonDeserializer deserializer(final Class clazz) { 28 | return new JacksonJsonDeserializer<>(mapper, clazz); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/main/java/com/jeromeloisel/db/conversion/jackson/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Json Conversion using Jackson. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.conversion.jackson; 8 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/Animal.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.fasterxml.jackson.annotation.JsonCreator; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.jeromeloisel.db.entity.Entity; 8 | 9 | import lombok.Builder; 10 | import lombok.Value; 11 | import lombok.experimental.Wither; 12 | 13 | @Value 14 | @Builder 15 | public class Animal implements Entity { 16 | 17 | @Wither 18 | String id; 19 | String name; 20 | String specy; 21 | boolean isNude; 22 | 23 | @JsonCreator 24 | public Animal( 25 | @JsonProperty("id") final String id, 26 | @JsonProperty("name") final String name, 27 | @JsonProperty("specy") final String specy, 28 | @JsonProperty("nude") final boolean isNude) { 29 | super(); 30 | this.id = checkNotNull(id); 31 | this.name = checkNotNull(name); 32 | this.specy = checkNotNull(specy); 33 | this.isNude = checkNotNull(isNude); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/ConversionTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 4 | import static org.junit.Assert.assertEquals; 5 | 6 | import org.junit.Test; 7 | 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.google.common.testing.NullPointerTester; 10 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 11 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 12 | 13 | public class ConversionTest { 14 | 15 | private final ObjectMapper mapper = new ObjectMapper(); 16 | private final JsonSerializer serializer = new JacksonJsonSerializer<>(mapper); 17 | private final JsonDeserializer deserializer = new JacksonJsonDeserializer<>(mapper, Animal.class); 18 | 19 | @Test 20 | public void shouldBijectivelyConvert() { 21 | final Animal animal = new Animal("id", "Tiger", "Panthera Tigris", false); 22 | final String json = serializer.apply(animal); 23 | final Animal deserialized = deserializer.apply(json); 24 | assertEquals(animal, deserialized); 25 | } 26 | 27 | @Test 28 | public void shouldPassNPETesterDeserializer() { 29 | new NullPointerTester().testConstructors(JacksonJsonDeserializer.class, PACKAGE); 30 | } 31 | 32 | @Test 33 | public void shouldPassNPETesterSerializer() { 34 | new NullPointerTester().testConstructors(JacksonJsonSerializer.class, PACKAGE); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/JacksonConversionExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.IOException; 7 | 8 | import org.junit.Test; 9 | 10 | import com.google.common.testing.NullPointerTester; 11 | 12 | public class JacksonConversionExceptionTest { 13 | 14 | @Test 15 | public void shouldPassNPETester() { 16 | new NullPointerTester().testConstructors(JacksonConversionException.class, PACKAGE); 17 | } 18 | 19 | @Test 20 | public void shouldThrow() { 21 | final Exception e = new JacksonConversionException("", new IOException()); 22 | assertNotNull(e); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/JacksonJsonDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.ObjectReader; 6 | import com.google.common.testing.NullPointerTester; 7 | import com.jeromeloisel.db.entity.Entity; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.Mock; 12 | import org.mockito.runners.MockitoJUnitRunner; 13 | 14 | import java.io.IOException; 15 | 16 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.when; 19 | 20 | @RunWith(MockitoJUnitRunner.class) 21 | public class JacksonJsonDeserializerTest { 22 | 23 | @Mock 24 | private ObjectMapper mapper; 25 | @Mock 26 | private Entity entity; 27 | @Mock 28 | private ObjectReader reader; 29 | 30 | private JacksonJsonDeserializer function; 31 | 32 | @Before 33 | public void before() { 34 | when(mapper.readerFor(Entity.class)).thenReturn(reader); 35 | function = new JacksonJsonDeserializer<>(mapper, Entity.class); 36 | } 37 | 38 | @Test 39 | public void shouldPassNPETesterSerializer() { 40 | new NullPointerTester().testConstructors(JacksonJsonDeserializer.class, PACKAGE); 41 | } 42 | 43 | @Test(expected=JacksonConversionException.class) 44 | public void shouldThrow() throws IOException { 45 | when(reader.readValue("")).thenThrow(mock(JsonProcessingException.class)); 46 | function.apply(""); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/JacksonJsonSerializerTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.google.common.testing.NullPointerTester; 6 | import com.jeromeloisel.db.entity.Entity; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | 13 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 14 | import static org.mockito.Mockito.mock; 15 | import static org.mockito.Mockito.when; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class JacksonJsonSerializerTest { 19 | 20 | @Mock 21 | private ObjectMapper mapper; 22 | @Mock 23 | private Entity entity; 24 | 25 | private JacksonJsonSerializer serializer; 26 | 27 | @Before 28 | public void before() { 29 | serializer = new JacksonJsonSerializer<>(mapper); 30 | } 31 | 32 | @Test 33 | public void shouldPassNPETesterSerializer() { 34 | new NullPointerTester().testConstructors(JacksonJsonSerializer.class, PACKAGE); 35 | } 36 | 37 | @Test(expected=JacksonConversionException.class) 38 | public void shouldThrow() throws JsonProcessingException { 39 | when(mapper.writeValueAsString(entity)).thenThrow(mock(JsonProcessingException.class)); 40 | serializer.apply(entity); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /db-conversion-jackson/src/test/java/com/jeromeloisel/db/conversion/jackson/JacksonSerializationFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.conversion.jackson; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.google.common.testing.NullPointerTester; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 12 | import static org.junit.Assert.assertEquals; 13 | 14 | @RunWith(MockitoJUnitRunner.class) 15 | public class JacksonSerializationFactoryTest { 16 | 17 | @Mock 18 | private ObjectMapper mapper; 19 | 20 | private JacksonSerializationFactory factory; 21 | 22 | @Before 23 | public void before() { 24 | factory = new JacksonSerializationFactory(mapper); 25 | } 26 | 27 | @Test 28 | public void shouldCreateDeserializer() { 29 | assertEquals(JacksonJsonDeserializer.class, factory.deserializer(Animal.class).getClass()); 30 | } 31 | 32 | @Test 33 | public void shouldCreateSerializer() { 34 | assertEquals(JacksonJsonSerializer.class, factory.serializer(Animal.class).getClass()); 35 | } 36 | 37 | @Test 38 | public void shouldPassNPETesterSerializer() { 39 | new NullPointerTester().testConstructors(JacksonSerializationFactory.class, PACKAGE); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /db-entity/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-entity/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-entity 10 | 11 | -------------------------------------------------------------------------------- /db-entity/src/main/java/com/jeromeloisel/db/entity/Document.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.entity; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Defines the index name and the type in which the document is stored. 12 | * 13 | * @author jerome 14 | * 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(TYPE) 19 | public @interface Document { 20 | 21 | /** 22 | * ElasticSearch index name. 23 | * 24 | * @return 25 | */ 26 | String indexName(); 27 | 28 | /** 29 | * Elasticsearch type associated to the annotated bean. 30 | * 31 | * @return 32 | */ 33 | String type(); 34 | } 35 | -------------------------------------------------------------------------------- /db-entity/src/main/java/com/jeromeloisel/db/entity/Entity.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.entity; 2 | 3 | /** 4 | * Representation of an immutable entity. 5 | * 6 | * @author jerome 7 | * 8 | */ 9 | public interface Entity { 10 | 11 | /** 12 | * Inject this field into the bean by using the {@link Id} annotation. 13 | * 14 | * @return id of the entity 15 | */ 16 | String getId(); 17 | 18 | /** 19 | * Returns a copy of the entity with this {@code id} set. 20 | * 21 | * @param id new id to set 22 | * @return 23 | */ 24 | Entity withId(String id); 25 | } 26 | -------------------------------------------------------------------------------- /db-entity/src/main/java/com/jeromeloisel/db/entity/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Beans stored on ElasticSearch implement this API. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.entity; 8 | -------------------------------------------------------------------------------- /db-integration-test/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-integration-test/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-integration-test 10 | 11 | 12 | 13 | org.elasticsearch.client 14 | transport 15 | 16 | 17 | org.springframework 18 | spring-context 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | ${spring.boot.version} 24 | 25 | 26 | com.fasterxml.jackson.core 27 | jackson-databind 28 | 29 | 30 | 31 | org.springframework 32 | spring-test 33 | compile 34 | 35 | 36 | com.jeromeloisel 37 | db-conversion-jackson 38 | ${project.version} 39 | 40 | 41 | junit 42 | junit 43 | compile 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-test 48 | ${spring.boot.version} 49 | compile 50 | 51 | 52 | org.apache.logging.log4j 53 | log4j-core 54 | ${log4j.version} 55 | 56 | 57 | -------------------------------------------------------------------------------- /db-integration-test/src/main/java/com/jeromeloisel/Application.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(final String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } -------------------------------------------------------------------------------- /db-integration-test/src/main/java/com/jeromeloisel/db/integration/test/NodeTestConfig.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.integration.test; 2 | 3 | import org.elasticsearch.client.Client; 4 | import org.elasticsearch.cluster.ClusterName; 5 | import org.elasticsearch.common.network.NetworkModule; 6 | import org.elasticsearch.common.settings.Settings; 7 | import org.elasticsearch.common.util.concurrent.EsExecutors; 8 | import org.elasticsearch.env.Environment; 9 | import org.elasticsearch.node.Node; 10 | import org.elasticsearch.node.NodeValidationException; 11 | import org.elasticsearch.script.ScriptService; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | import java.nio.file.Path; 17 | 18 | import static com.google.common.io.Files.createTempDir; 19 | import static org.elasticsearch.env.NodeEnvironment.NODE_ID_SEED_SETTING; 20 | 21 | @Configuration 22 | class NodeTestConfig { 23 | 24 | @Bean(destroyMethod="close") 25 | Node newNode() throws NodeValidationException { 26 | final Path tempDir = createTempDir().toPath(); 27 | final Settings settings = Settings.builder() 28 | .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), new ClusterName("single-node-cluster" + System.nanoTime())) 29 | .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) 30 | .put(Environment.PATH_REPO_SETTING.getKey(), tempDir.resolve("repo")) 31 | .put(Environment.PATH_SHARED_DATA_SETTING.getKey(), createTempDir().getParent()) 32 | .put("node.name", "single-node") 33 | .put("script.inline", "true") 34 | .put("script.stored", "true") 35 | .put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 1000) 36 | .put(EsExecutors.PROCESSORS_SETTING.getKey(), 1) 37 | .put(NetworkModule.HTTP_ENABLED.getKey(), false) 38 | .put("discovery.type", "zen") 39 | .put("transport.type", "local") 40 | .put(Node.NODE_DATA_SETTING.getKey(), true) 41 | .put(NODE_ID_SEED_SETTING.getKey(), System.nanoTime()) 42 | .build(); 43 | return new Node(settings).start(); // NOSONAR 44 | } 45 | 46 | @Bean(destroyMethod = "close") 47 | @Autowired 48 | Client client(final Node node) { 49 | return node.client(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /db-integration-test/src/main/java/com/jeromeloisel/db/integration/test/SpringElasticSearchTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.integration.test; 2 | 3 | import com.jeromeloisel.Application; 4 | import org.elasticsearch.client.Client; 5 | import org.elasticsearch.client.IndicesAdminClient; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import static org.junit.Assert.assertNotNull; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest(classes=Application.class) 16 | public abstract class SpringElasticSearchTest { 17 | @Autowired 18 | protected Client client; 19 | 20 | @Test 21 | public void shouldAutowireClient() { 22 | assertNotNull(client); 23 | } 24 | 25 | protected final Client elasticClient() { 26 | return client; 27 | } 28 | 29 | protected final void flush(final String index) { 30 | final IndicesAdminClient indices = elasticClient().admin().indices(); 31 | indices.prepareFlush(index).execute().actionGet(); 32 | indices.prepareRefresh(index).execute().actionGet(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /db-integration-test/src/main/java/com/jeromeloisel/db/integration/test/TestConversionConfig.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.integration.test; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 9 | import com.jeromeloisel.db.conversion.jackson.JacksonSerializationFactory; 10 | 11 | @Configuration 12 | class TestConversionConfig { 13 | 14 | @Bean 15 | ObjectMapper objectMapper() { 16 | final ObjectMapper mapper = new ObjectMapper(); 17 | mapper.findAndRegisterModules(); 18 | return mapper; 19 | } 20 | 21 | @Bean 22 | @Autowired 23 | JsonSerializationFactory jsonSerializationFactory(final ObjectMapper mapper) { 24 | return new JacksonSerializationFactory(mapper); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /db-integration-test/src/main/java/com/jeromeloisel/db/integration/test/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Database Repository Test module. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.integration.test; 8 | -------------------------------------------------------------------------------- /db-integration-test/src/test/java/com/jeromeloisel/db/integration/test/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.integration.test; 2 | 3 | import com.jeromeloisel.Application; 4 | import org.junit.Test; 5 | 6 | public class ApplicationTest { 7 | 8 | @Test 9 | public void shouldRunMain() throws Exception { 10 | Application.main(new String[0]); 11 | } 12 | } -------------------------------------------------------------------------------- /db-integration-test/src/test/java/com/jeromeloisel/db/integration/test/ElasticTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.integration.test; 2 | 3 | import org.elasticsearch.client.IndicesAdminClient; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertNotNull; 7 | 8 | public class ElasticTest extends SpringElasticSearchTest { 9 | 10 | @Test 11 | public void shouldAutowire() { 12 | assertNotNull(client); 13 | final IndicesAdminClient indices = client.admin().indices(); 14 | indices.prepareCreate("test").execute().actionGet(); 15 | flush("test"); 16 | indices.prepareDelete("test").execute().actionGet(); 17 | } 18 | 19 | @Test 20 | @Override 21 | public void shouldAutowireClient() { 22 | super.shouldAutowireClient(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /db-repository-api/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-repository-api/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-repository-api 10 | 11 | 12 | 13 | com.jeromeloisel 14 | db-entity 15 | ${project.version} 16 | 17 | 18 | org.elasticsearch.client 19 | transport 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /db-repository-api/src/main/java/com/jeromeloisel/db/repository/api/DatabaseRepository.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.api; 2 | 3 | import com.jeromeloisel.db.entity.Entity; 4 | import org.elasticsearch.action.support.WriteRequest; 5 | import org.elasticsearch.index.query.QueryBuilder; 6 | 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.function.Consumer; 11 | 12 | /** 13 | * Basic Crud Database repository. 14 | * 15 | * @author jerome 16 | * 17 | * @param type of entity to store 18 | */ 19 | public interface DatabaseRepository { 20 | 21 | /** 22 | * Sets the index refresh setting. 23 | * 24 | * @param refresh refresh policy 25 | */ 26 | void refreshPolicy(WriteRequest.RefreshPolicy refresh); 27 | 28 | /** 29 | * Searches using the given request. 30 | * 31 | * @param query 32 | * @return matching items 33 | */ 34 | List search(QueryBuilder query); 35 | 36 | /** 37 | * Saves a given result. Use the returned instance for further operations as the save operation might have changed the 38 | * result instance completely. 39 | * 40 | * @param entity 41 | * @return the saved result 42 | */ 43 | T save(T entity); 44 | 45 | /** 46 | * Saves all given entities. 47 | * 48 | * @param entities 49 | * @return the saved entities 50 | * @throws IllegalArgumentException in case the given result is (@literal null}. 51 | */ 52 | List saveAll(List entities); 53 | 54 | /** 55 | * Retrieves an result by its id. 56 | * 57 | * @param id must not be {@literal null}. 58 | * @return the result with the given id or {@literal null} if none found 59 | * @throws IllegalArgumentException if {@code id} is {@literal null} 60 | */ 61 | Optional findOne(String id); 62 | 63 | /** 64 | * Returns whether an result with the given id exists. 65 | * 66 | * @param id must not be {@literal null}. 67 | * @return true if an result with the given id exists, {@literal false} otherwise 68 | * @throws IllegalArgumentException if {@code id} is {@literal null} 69 | */ 70 | boolean exists(String id); 71 | 72 | /** 73 | * Returns whether an result exists. 74 | * 75 | * @param entity must not be {@code null} 76 | * @return true if an result exists, {@literal false} otherwise 77 | * @throws IllegalArgumentException if {@code id} is {@literal null} 78 | */ 79 | boolean exists(T entity); 80 | 81 | /** 82 | * Returns all instances of the type with the given IDs. 83 | * 84 | * @param ids 85 | * @return 86 | */ 87 | List findAll(List ids); 88 | 89 | /** 90 | * Deletes the result with the given id. 91 | * 92 | * @param id must not be {@literal null}. 93 | * @return the deleted result id 94 | * @throws IllegalArgumentException in case the given {@code id} is {@literal null} 95 | */ 96 | String delete(String id); 97 | 98 | /** 99 | * Deletes a given result. 100 | * 101 | * @param entity 102 | * @return the deleted result id 103 | * @throws IllegalArgumentException in case the given result is (@literal null}. 104 | */ 105 | String delete(T entity); 106 | 107 | /** 108 | * Deletes all the given entities at once. 109 | * 110 | * @param entities 111 | */ 112 | List deleteAll(Collection entities); 113 | 114 | /** 115 | * Deletes all the given entities at once. 116 | * 117 | * @param ids 118 | */ 119 | List deleteAllIds(Collection ids); 120 | 121 | /** 122 | * Deletes all the entities matching the given query. 123 | * 124 | * @param query query 125 | * @return matching items 126 | */ 127 | void deleteAllByQuery(QueryBuilder query); 128 | 129 | /** 130 | * Scrolls over the whole results and consume them. 131 | * 132 | * @param query elastic query 133 | * @param consumer consumes each item 134 | */ 135 | void scroll(QueryBuilder query, Consumer consumer); 136 | } 137 | -------------------------------------------------------------------------------- /db-repository-api/src/main/java/com/jeromeloisel/db/repository/api/DatabaseRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.api; 2 | 3 | import com.jeromeloisel.db.entity.Entity; 4 | 5 | public interface DatabaseRepositoryFactory { 6 | 7 | DatabaseRepository create(Class clazz); 8 | } 9 | -------------------------------------------------------------------------------- /db-repository-api/src/main/java/com/jeromeloisel/db/repository/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Elasticsearch Repository API. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.repository.api; 8 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-repository-elasticsearch 10 | 11 | 12 | 13 | com.jeromeloisel 14 | db-repository-api 15 | ${project.version} 16 | 17 | 18 | com.jeromeloisel 19 | db-conversion-api 20 | ${project.version} 21 | 22 | 23 | com.jeromeloisel 24 | db-scroll-api 25 | ${project.version} 26 | 27 | 28 | 29 | org.elasticsearch.client 30 | transport 31 | 32 | 33 | 34 | com.jeromeloisel 35 | db-integration-test 36 | ${project.version} 37 | test 38 | 39 | 40 | com.jeromeloisel 41 | db-scroll-elastic 42 | ${project.version} 43 | test 44 | 45 | 46 | commons-io 47 | commons-io 48 | test 49 | 50 | 51 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/main/java/com/jeromeloisel/db/repository/elasticsearch/ElasticSearchRepository.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.ImmutableList.Builder; 6 | import com.google.common.collect.ImmutableSet; 7 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 8 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 9 | import com.jeromeloisel.db.entity.Entity; 10 | import com.jeromeloisel.db.repository.api.DatabaseRepository; 11 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 12 | import lombok.AllArgsConstructor; 13 | import lombok.NonNull; 14 | import lombok.experimental.FieldDefaults; 15 | import org.elasticsearch.action.bulk.BulkItemResponse; 16 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 17 | import org.elasticsearch.action.bulk.BulkResponse; 18 | import org.elasticsearch.action.get.GetResponse; 19 | import org.elasticsearch.action.get.MultiGetItemResponse; 20 | import org.elasticsearch.action.get.MultiGetResponse; 21 | import org.elasticsearch.action.index.IndexRequestBuilder; 22 | import org.elasticsearch.action.support.WriteRequest; 23 | import org.elasticsearch.client.Client; 24 | import org.elasticsearch.index.query.QueryBuilder; 25 | 26 | import java.util.Collection; 27 | import java.util.List; 28 | import java.util.Optional; 29 | import java.util.concurrent.atomic.AtomicReference; 30 | import java.util.function.Consumer; 31 | 32 | import static com.google.common.base.Strings.emptyToNull; 33 | import static java.util.Optional.ofNullable; 34 | import static java.util.stream.Collectors.toList; 35 | import static lombok.AccessLevel.PACKAGE; 36 | import static lombok.AccessLevel.PRIVATE; 37 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 38 | 39 | @AllArgsConstructor(access = PACKAGE) 40 | @FieldDefaults(level = PRIVATE, makeFinal = true) 41 | final class ElasticSearchRepository implements DatabaseRepository { 42 | @VisibleForTesting 43 | public static final int SCROLL_SIZE = 100; 44 | 45 | @NonNull 46 | String index; 47 | @NonNull 48 | String type; 49 | @NonNull 50 | Client client; 51 | @NonNull 52 | JsonSerializer serializer; 53 | @NonNull 54 | JsonDeserializer deserializer; 55 | @NonNull 56 | DatabaseScrollingFactory scrolling; 57 | @NonNull 58 | AtomicReference policy; 59 | 60 | @Override 61 | public List search(final QueryBuilder query) { 62 | final Builder builder = ImmutableList.builder(); 63 | scroll(query, builder::add); 64 | return builder.build(); 65 | } 66 | 67 | @Override 68 | public void deleteAllByQuery(final QueryBuilder query) { 69 | final Builder builder = ImmutableList.builder(); 70 | scroll(query, e -> builder.add(e.getId())); 71 | final List ids = builder.build(); 72 | if(ids.isEmpty()) { 73 | return; 74 | } 75 | 76 | deleteAllIds(ids); 77 | } 78 | 79 | @Override 80 | public void scroll(final QueryBuilder query, final Consumer consumer) { 81 | scrolling 82 | .newScroll(index) 83 | .withQuery(query) 84 | .withFetchSource(true) 85 | .withTypes(type) 86 | .scroll(new EntityScroll<>(deserializer, consumer)); 87 | } 88 | 89 | @Override 90 | public T save(final T entity) { 91 | return saveAll(ImmutableList.of(entity)).get(0); 92 | } 93 | 94 | @Override 95 | public List saveAll(final List entities) { 96 | if(entities.isEmpty()) { 97 | return entities; 98 | } 99 | 100 | final BulkRequestBuilder bulk = client 101 | .prepareBulk() 102 | .setRefreshPolicy(policy.get()); 103 | 104 | for(final T entity : entities) { 105 | final String source = serializer.apply(entity); 106 | final IndexRequestBuilder request = client 107 | .prepareIndex(index, type) 108 | .setSource(source, JSON); 109 | ofNullable(emptyToNull(entity.getId())).ifPresent(request::setId); 110 | bulk.add(request); 111 | } 112 | 113 | final BulkResponse response = bulk.execute().actionGet(); 114 | final BulkItemResponse[] items = response.getItems(); 115 | 116 | final ImmutableList.Builder saved = ImmutableList.builder(); 117 | for(int i=0; i findOne(final String id) { 144 | final GetResponse response = client 145 | .prepareGet(index, type, id) 146 | .setFetchSource(true) 147 | .execute() 148 | .actionGet(); 149 | final String json = response.getSourceAsString(); 150 | return ofNullable(json) 151 | .map(deserializer) 152 | .map(e -> (T) e.withId(id)); 153 | } 154 | 155 | @SuppressWarnings("unchecked") 156 | @Override 157 | public List findAll(final List ids) { 158 | if (ids.isEmpty()) { 159 | return ImmutableList.of(); 160 | } 161 | 162 | final Builder builder = ImmutableList.builder(); 163 | 164 | final MultiGetResponse response = client 165 | .prepareMultiGet() 166 | .add(index, type, ids) 167 | .execute() 168 | .actionGet(); 169 | 170 | for(final MultiGetItemResponse item : response.getResponses()) { 171 | final GetResponse get = item.getResponse(); 172 | if(get.isSourceEmpty()) { 173 | continue; 174 | } 175 | 176 | final String json = get.getSourceAsString(); 177 | final T entity = deserializer.apply(json); 178 | builder.add((T) entity.withId(get.getId())); 179 | } 180 | 181 | return builder.build(); 182 | } 183 | 184 | @Override 185 | public String delete(final T entity) { 186 | return deleteAll(ImmutableList.of(entity)).get(0); 187 | } 188 | 189 | @Override 190 | public List deleteAll(final Collection entities) { 191 | return deleteAllIds(entities.stream().map(Entity::getId).collect(toList())); 192 | } 193 | 194 | @Override 195 | public String delete(final String id) { 196 | return deleteAllIds(ImmutableSet.of(id)).get(0); 197 | } 198 | 199 | @Override 200 | public List deleteAllIds(final Collection ids) { 201 | if (ids.isEmpty()) { 202 | return ImmutableList.of(); 203 | } 204 | 205 | final BulkRequestBuilder bulk = client 206 | .prepareBulk() 207 | .setRefreshPolicy(policy.get()); 208 | 209 | for (final String id : ids) { 210 | bulk.add(client.prepareDelete(index, type, id)); 211 | } 212 | 213 | final BulkResponse response = bulk.execute().actionGet(); 214 | 215 | final ImmutableList.Builder builder = ImmutableList.builder(); 216 | for (final BulkItemResponse item : response.getItems()) { 217 | builder.add(item.getId()); 218 | } 219 | return builder.build(); 220 | } 221 | 222 | @Override 223 | public void refreshPolicy(final WriteRequest.RefreshPolicy refresh) { 224 | policy.set(refresh); 225 | } 226 | } -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/main/java/com/jeromeloisel/db/repository/elasticsearch/ElasticSearchRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 4 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 5 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 6 | import com.jeromeloisel.db.entity.Document; 7 | import com.jeromeloisel.db.entity.Entity; 8 | import com.jeromeloisel.db.repository.api.DatabaseRepository; 9 | import com.jeromeloisel.db.repository.api.DatabaseRepositoryFactory; 10 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 11 | import lombok.AllArgsConstructor; 12 | import lombok.NonNull; 13 | import lombok.experimental.FieldDefaults; 14 | import org.elasticsearch.client.Client; 15 | 16 | import java.util.concurrent.atomic.AtomicReference; 17 | 18 | import static com.google.common.base.Preconditions.checkState; 19 | import static lombok.AccessLevel.PRIVATE; 20 | import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; 21 | 22 | @AllArgsConstructor 23 | @FieldDefaults(level=PRIVATE, makeFinal = true) 24 | public final class ElasticSearchRepositoryFactory implements DatabaseRepositoryFactory { 25 | @NonNull 26 | JsonSerializationFactory factory; 27 | @NonNull 28 | Client client; 29 | @NonNull 30 | DatabaseScrollingFactory scrolling; 31 | 32 | @Override 33 | public DatabaseRepository create(final Class clazz) { 34 | checkState( 35 | clazz.isAnnotationPresent(Document.class), 36 | "%s must be annotated with @Document", 37 | clazz.getName()); 38 | 39 | final Document document = clazz.getDeclaredAnnotation(Document.class); 40 | final JsonSerializer serializer = factory.serializer(clazz); 41 | final JsonDeserializer deserializer = factory.deserializer(clazz); 42 | 43 | final String index = document.indexName(); 44 | final String type = document.type(); 45 | 46 | return new ElasticSearchRepository<>( 47 | index, 48 | type, 49 | client, 50 | serializer, 51 | deserializer, 52 | scrolling, 53 | new AtomicReference<>(IMMEDIATE) 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/main/java/com/jeromeloisel/db/repository/elasticsearch/EntityScroll.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonDeserializer; 4 | import com.jeromeloisel.db.entity.Entity; 5 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 6 | import lombok.AllArgsConstructor; 7 | import lombok.NonNull; 8 | import lombok.experimental.FieldDefaults; 9 | import org.elasticsearch.search.SearchHit; 10 | 11 | import java.util.function.Consumer; 12 | 13 | import static lombok.AccessLevel.PACKAGE; 14 | import static lombok.AccessLevel.PRIVATE; 15 | 16 | @AllArgsConstructor(access = PACKAGE) 17 | @FieldDefaults(level = PRIVATE, makeFinal = true) 18 | final class EntityScroll implements DatabaseScroll { 19 | @NonNull 20 | JsonDeserializer deserializer; 21 | @NonNull 22 | Consumer consumer; 23 | 24 | @Override 25 | public void accept(final SearchHit hit) { 26 | final String source = hit.getSourceAsString(); 27 | @SuppressWarnings("unchecked") 28 | final T entity = (T) deserializer.apply(source).withId(hit.getId()); 29 | consumer.accept(entity); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/main/java/com/jeromeloisel/db/repository/elasticsearch/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Elasticsearch Database Repository. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.db.repository.elasticsearch; 8 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/ElasticSearchRepositoryFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 4 | 5 | import org.junit.Test; 6 | 7 | import com.google.common.testing.NullPointerTester; 8 | 9 | public class ElasticSearchRepositoryFactoryTest { 10 | 11 | @Test 12 | public void shouldPassNPETester() { 13 | new NullPointerTester().testConstructors(ElasticSearchRepositoryFactory.class, PACKAGE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/ElasticSearchRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 4 | 5 | import org.junit.Test; 6 | 7 | import com.google.common.testing.NullPointerTester; 8 | 9 | public class ElasticSearchRepositoryTest { 10 | 11 | @Test 12 | public void shouldPassNPETester() { 13 | new NullPointerTester().testConstructors(ElasticSearchRepository.class, PACKAGE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/EntityScrollTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.google.common.testing.NullPointerTester; 4 | import org.junit.Test; 5 | 6 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 7 | 8 | public class EntityScrollTest { 9 | 10 | @Test 11 | public void shouldPassNPETester() { 12 | new NullPointerTester().testConstructors(EntityScroll.class, PACKAGE); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/Person.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.fasterxml.jackson.annotation.JsonCreator; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.jeromeloisel.db.entity.Document; 8 | import com.jeromeloisel.db.entity.Entity; 9 | 10 | import lombok.Builder; 11 | import lombok.Value; 12 | import lombok.experimental.Wither; 13 | 14 | @Value 15 | @Builder 16 | @Document(indexName="datas", type="person") 17 | public class Person implements Entity { 18 | @Wither 19 | String id; 20 | String firstname; 21 | String lastname; 22 | 23 | @JsonCreator 24 | Person( 25 | @JsonProperty("id") final String id, 26 | @JsonProperty("firstname") final String firstname, 27 | @JsonProperty("lastname") final String lastname) { 28 | super(); 29 | this.id = id; 30 | this.firstname = checkNotNull(firstname); 31 | this.lastname = checkNotNull(lastname); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/RepositoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.jeromeloisel.Application; 5 | import com.jeromeloisel.db.repository.api.DatabaseRepository; 6 | import com.jeromeloisel.db.repository.api.DatabaseRepositoryFactory; 7 | import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest; 8 | import org.elasticsearch.client.Client; 9 | import org.elasticsearch.client.IndicesAdminClient; 10 | import org.elasticsearch.index.query.BoolQueryBuilder; 11 | import org.elasticsearch.index.query.MatchAllQueryBuilder; 12 | import org.elasticsearch.index.query.TermQueryBuilder; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | 21 | import java.io.IOException; 22 | import java.util.List; 23 | 24 | import static com.google.common.base.Preconditions.checkState; 25 | import static org.apache.commons.io.IOUtils.toByteArray; 26 | import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; 27 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.assertFalse; 30 | import static org.junit.Assert.assertNotEquals; 31 | import static org.junit.Assert.assertTrue; 32 | 33 | @RunWith(SpringRunner.class) 34 | @SpringBootTest(classes=Application.class) 35 | public class RepositoryIntegrationTest { 36 | 37 | private static final Person PERSON = Person 38 | .builder() 39 | .id("") 40 | .firstname("John") 41 | .lastname("Smith") 42 | .build(); 43 | 44 | @Autowired 45 | private Client client; 46 | @Autowired 47 | private DatabaseRepositoryFactory factory; 48 | 49 | private DatabaseRepository repository; 50 | 51 | @Before 52 | public void before() throws IOException { 53 | repository = factory.create(Person.class); 54 | repository.refreshPolicy(IMMEDIATE); 55 | final IndicesAdminClient indices = client.admin().indices(); 56 | 57 | final PutIndexTemplateRequest datas = indices.preparePutTemplate("datas") 58 | .setSource(toByteArray(getClass().getResourceAsStream("/datas.json")), JSON) 59 | .request(); 60 | checkState(indices.putTemplate(datas).actionGet().isAcknowledged()); 61 | } 62 | 63 | @Test 64 | public void shouldSaveTwice() { 65 | final ImmutableList.Builder builder = ImmutableList.builder(); 66 | for(int i=0; i< ElasticSearchRepository.SCROLL_SIZE * 2;i++) { 67 | builder.add(PERSON); 68 | } 69 | final List persons = builder.build(); 70 | 71 | final List saved = repository.saveAll(persons); 72 | assertEquals(persons.size(), saved.size()); 73 | 74 | final List found = repository.search(new MatchAllQueryBuilder()); 75 | assertEquals(persons.size(), found.size()); 76 | 77 | repository.deleteAll(saved); 78 | } 79 | 80 | @Test 81 | public void shouldDeleteAllByQuery() { 82 | final Person saved = repository.save(PERSON); 83 | final TermQueryBuilder byFirstname = new TermQueryBuilder("firstname", PERSON.getFirstname()); 84 | repository.deleteAllByQuery(byFirstname); 85 | assertFalse(repository.exists(saved)); 86 | } 87 | 88 | @Test 89 | public void shouldNotDeleteAllByIds() { 90 | final List ids = repository.deleteAllIds(ImmutableList.of()); 91 | assertTrue(ids.isEmpty()); 92 | } 93 | 94 | @Test 95 | public void shouldNotDeleteAllByQuery() { 96 | final Person saved = repository.save(PERSON); 97 | final TermQueryBuilder byFirstname = new TermQueryBuilder("firstname", PERSON.getLastname()); 98 | repository.deleteAllByQuery(byFirstname); 99 | assertTrue(repository.exists(saved)); 100 | repository.delete(saved); 101 | } 102 | 103 | @Test 104 | public void shouldNotDeleteById() { 105 | final Person saved = repository.save(PERSON); 106 | repository.delete(saved.getId() + "invalid"); 107 | assertTrue(repository.exists(saved)); 108 | repository.delete(saved.getId()); 109 | assertFalse(repository.exists(saved)); 110 | } 111 | 112 | @Test 113 | public void shouldScanAndScroll() { 114 | final Person saved = repository.save(PERSON); 115 | final Person anotherSave = repository.save(saved); 116 | assertEquals(anotherSave.getId(), saved.getId()); 117 | repository.delete(saved); 118 | } 119 | 120 | @Test 121 | public void shouldDeleteById() { 122 | final Person saved = repository.save(PERSON); 123 | repository.delete(saved.getId()); 124 | assertFalse(repository.exists(saved)); 125 | } 126 | 127 | @Test 128 | public void shouldExistsById() { 129 | final Person saved = repository.save(PERSON); 130 | assertTrue(repository.exists(saved.getId())); 131 | repository.delete(saved.getId()); 132 | assertFalse(repository.exists(saved.getId())); 133 | } 134 | 135 | @Test 136 | public void shouldFindOne() { 137 | final Person save = repository.save(PERSON); 138 | assertEquals(save, repository.findOne(save.getId()).get()); 139 | repository.delete(save); 140 | } 141 | 142 | @Test 143 | public void shouldFindAll() { 144 | final Person save = repository.save(PERSON); 145 | assertEquals(ImmutableList.of(save), repository.findAll(ImmutableList.of(save.getId()))); 146 | repository.delete(save); 147 | } 148 | 149 | @Test 150 | public void shouldNotFindAll() { 151 | assertEquals(ImmutableList.of(), repository.findAll(ImmutableList.of())); 152 | } 153 | 154 | @Test 155 | public void shouldNotFindAllIfInvalid() { 156 | assertEquals(ImmutableList.of(), repository.findAll(ImmutableList.of("invalid"))); 157 | } 158 | 159 | @Test 160 | public void shouldSaveAll() { 161 | final List saved = repository.saveAll(ImmutableList.of(PERSON)); 162 | assertEquals(1, saved.size()); 163 | repository.delete(saved.get(0)); 164 | } 165 | 166 | @Test 167 | public void shouldNotSaveAll() { 168 | final List saved = repository.saveAll(ImmutableList.of()); 169 | assertEquals(0, saved.size()); 170 | } 171 | 172 | @Test 173 | public void shouldFindByFirstname() { 174 | final Person saved = repository.save(PERSON); 175 | final TermQueryBuilder term = new TermQueryBuilder("firstname", PERSON.getFirstname()); 176 | 177 | final List search = repository.search(term); 178 | assertEquals(1, search.size()); 179 | assertEquals(saved, search.get(0)); 180 | repository.delete(saved); 181 | } 182 | 183 | @Test 184 | public void shouldFindByFirstnameAndLastname() { 185 | final Person saved = repository.save(PERSON); 186 | final TermQueryBuilder byFirstname = new TermQueryBuilder("firstname", PERSON.getFirstname()); 187 | final TermQueryBuilder byLastname = new TermQueryBuilder("lastname", PERSON.getLastname()); 188 | final BoolQueryBuilder bool = new BoolQueryBuilder() 189 | .must(byFirstname) 190 | .must(byLastname); 191 | 192 | final List search = repository.search(bool); 193 | assertEquals(1, search.size()); 194 | assertEquals(saved, search.get(0)); 195 | repository.delete(saved); 196 | } 197 | 198 | @Test 199 | public void shouldTest() { 200 | final Person saved = repository.save(PERSON); 201 | assertNotEquals("", saved.getId()); 202 | final String id = repository.delete(saved); 203 | assertEquals(saved.getId(), id); 204 | } 205 | 206 | @After 207 | public void after() { 208 | repository.delete(PERSON); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/java/com/jeromeloisel/db/repository/elasticsearch/TestRepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.repository.elasticsearch; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 4 | import com.jeromeloisel.db.repository.api.DatabaseRepositoryFactory; 5 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 6 | import org.elasticsearch.client.Client; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | class TestRepositoryConfig { 12 | 13 | @Bean 14 | DatabaseRepositoryFactory databaseRepositoryFactory( 15 | final Client client, 16 | final JsonSerializationFactory serialization, 17 | final DatabaseScrollingFactory factory) { 18 | return new ElasticSearchRepositoryFactory(serialization, client, factory); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /db-repository-elasticsearch/src/test/resources/datas.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "datas", 3 | "settings": { 4 | "number_of_shards": 1, 5 | "number_of_replicas": 1, 6 | "index.refresh_interval": -1 7 | }, 8 | "mappings": { 9 | "_default_": { 10 | "_all": { 11 | "enabled": false 12 | }, 13 | "_source": { 14 | "enabled": true 15 | }, 16 | "dynamic_templates": [ 17 | { 18 | "strings": { 19 | "match_mapping_type": "string", 20 | "mapping": { 21 | "type": "keyword" 22 | } 23 | } 24 | } 25 | ] 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /db-scroll-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | elasticsearch-crud 7 | com.jeromeloisel 8 | 5.6.4-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | db-scroll-api 13 | 14 | 15 | 16 | org.elasticsearch.client 17 | transport 18 | 19 | 20 | -------------------------------------------------------------------------------- /db-scroll-api/src/main/java/com/jeromeloisel/db/scroll/api/DatabaseScroll.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.scroll.api; 2 | 3 | import org.elasticsearch.search.SearchHit; 4 | 5 | import java.io.IOException; 6 | 7 | @FunctionalInterface 8 | public interface DatabaseScroll { 9 | 10 | default void onStartBatch() throws IOException { 11 | 12 | } 13 | 14 | void accept(SearchHit hit) throws IOException; 15 | 16 | default void onEndBatch() throws IOException { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /db-scroll-api/src/main/java/com/jeromeloisel/db/scroll/api/DatabaseScrolling.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.scroll.api; 2 | 3 | import org.elasticsearch.index.query.QueryBuilder; 4 | import org.elasticsearch.search.sort.FieldSortBuilder; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public interface DatabaseScrolling { 9 | 10 | /** 11 | * Defaults to _doc sort. 12 | * 13 | * @param sort 14 | * @return 15 | */ 16 | DatabaseScrolling withSort(FieldSortBuilder sort); 17 | 18 | /** 19 | * Defaults to 100. 20 | * 21 | * @param size 22 | * @return 23 | */ 24 | DatabaseScrolling withScrollSize(int size); 25 | 26 | /** 27 | * Defaults to 1 minute. 28 | * 29 | * @param time 30 | * @param unit 31 | * @return 32 | */ 33 | DatabaseScrolling withKeepAlive(long time, TimeUnit unit); 34 | 35 | /** 36 | * Defaults to all index types. 37 | * @param types 38 | * @return 39 | */ 40 | DatabaseScrolling withTypes(String... types); 41 | 42 | /** 43 | * Defaults to true. 44 | * 45 | * @param fetchSource if source should be fetched 46 | * @return 47 | */ 48 | DatabaseScrolling withFetchSource(boolean fetchSource); 49 | 50 | /** 51 | * By default, uses {@link org.elasticsearch.index.query.MatchAllQueryBuilder}. 52 | * 53 | * @param query the query 54 | */ 55 | DatabaseScrolling withQuery(QueryBuilder query); 56 | 57 | void scroll(DatabaseScroll scroll); 58 | } 59 | -------------------------------------------------------------------------------- /db-scroll-api/src/main/java/com/jeromeloisel/db/scroll/api/DatabaseScrollingFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.scroll.api; 2 | 3 | import org.elasticsearch.action.search.SearchRequestBuilder; 4 | 5 | public interface DatabaseScrollingFactory { 6 | 7 | DatabaseScrolling newScroll(String index); 8 | 9 | DatabaseScrolling newScroll(SearchRequestBuilder search); 10 | 11 | DatabaseScroll bulkDelete(); 12 | 13 | DatabaseScroll bulkIndex(); 14 | 15 | DatabaseScroll combine(DatabaseScroll... first); 16 | } 17 | -------------------------------------------------------------------------------- /db-scroll-api/src/test/java/com/jeromeloisel/db/scroll/api/DatabaseScrollTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.scroll.api; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | public class DatabaseScrollTest { 9 | 10 | private DatabaseScroll scroll = hit -> { 11 | 12 | }; 13 | 14 | @Test 15 | public void shouldOnStart() throws IOException { 16 | scroll.onStartBatch(); 17 | } 18 | 19 | @Test 20 | public void shouldOnend() throws IOException { 21 | scroll.onEndBatch(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db-scroll-api/src/test/java/com/jeromeloisel/db/scroll/api/MockDatabaseScroll.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.db.scroll.api; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import com.jeromeloisel.db.scroll.api.DatabaseScrolling; 5 | import org.elasticsearch.index.query.QueryBuilder; 6 | import org.elasticsearch.search.sort.FieldSortBuilder; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public final class MockDatabaseScroll implements DatabaseScrolling { 11 | @Override 12 | public DatabaseScrolling withSort(FieldSortBuilder sort) { 13 | return this; 14 | } 15 | 16 | @Override 17 | public DatabaseScrolling withScrollSize(int size) { 18 | return this; 19 | } 20 | 21 | @Override 22 | public DatabaseScrolling withKeepAlive(long time, TimeUnit unit) { 23 | return this; 24 | } 25 | 26 | @Override 27 | public DatabaseScrolling withTypes(String... types) { 28 | return this; 29 | } 30 | 31 | @Override 32 | public DatabaseScrolling withFetchSource(boolean fetchSource) { 33 | return this; 34 | } 35 | 36 | @Override 37 | public DatabaseScrolling withQuery(QueryBuilder query) { 38 | return this; 39 | } 40 | 41 | @Override 42 | public void scroll(final DatabaseScroll scroll) { 43 | // does nothing 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /db-scroll-elastic/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | elasticsearch-crud 7 | com.jeromeloisel 8 | 5.6.4-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | db-scroll-elastic 13 | 14 | 15 | 16 | com.jeromeloisel 17 | db-scroll-api 18 | ${project.version} 19 | 20 | 21 | org.apache.logging.log4j 22 | log4j-api 23 | 24 | 25 | org.springframework 26 | spring-context 27 | 28 | 29 | 30 | org.apache.commons 31 | commons-lang3 32 | test 33 | 34 | 35 | com.jeromeloisel 36 | db-integration-test 37 | ${project.version} 38 | test 39 | 40 | 41 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/BulkDelete.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NonNull; 6 | import lombok.experimental.FieldDefaults; 7 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 8 | import org.elasticsearch.action.delete.DeleteRequest; 9 | import org.elasticsearch.client.Client; 10 | import org.elasticsearch.search.SearchHit; 11 | 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | import static lombok.AccessLevel.PACKAGE; 15 | import static lombok.AccessLevel.PRIVATE; 16 | 17 | @AllArgsConstructor(access = PACKAGE) 18 | @FieldDefaults(level = PRIVATE, makeFinal = true) 19 | final class BulkDelete implements DatabaseScroll { 20 | @NonNull 21 | Client client; 22 | 23 | AtomicReference request = new AtomicReference<>(); 24 | 25 | @Override 26 | public void onStartBatch() { 27 | request.set(client.prepareBulk()); 28 | } 29 | 30 | @Override 31 | public void accept(final SearchHit hit) { 32 | final DeleteRequest delete = client 33 | .prepareDelete() 34 | .setIndex(hit.getIndex()) 35 | .setType(hit.getType()) 36 | .setId(hit.getId()) 37 | .request(); 38 | 39 | request.get().add(delete); 40 | } 41 | 42 | @Override 43 | public void onEndBatch() { 44 | request.get().execute().actionGet(); 45 | } 46 | } -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/BulkIndex.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NonNull; 6 | import lombok.experimental.FieldDefaults; 7 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 8 | import org.elasticsearch.action.index.IndexRequest; 9 | import org.elasticsearch.client.Client; 10 | import org.elasticsearch.search.SearchHit; 11 | 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | import static lombok.AccessLevel.PACKAGE; 15 | import static lombok.AccessLevel.PRIVATE; 16 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 17 | 18 | @AllArgsConstructor(access = PACKAGE) 19 | @FieldDefaults(level = PRIVATE, makeFinal = true) 20 | final class BulkIndex implements DatabaseScroll { 21 | @NonNull 22 | Client client; 23 | 24 | AtomicReference request = new AtomicReference<>(); 25 | 26 | @Override 27 | public void onStartBatch() { 28 | request.set(client.prepareBulk()); 29 | } 30 | 31 | @Override 32 | public void accept(final SearchHit hit) { 33 | final IndexRequest index = client 34 | .prepareIndex(hit.getIndex(), hit.getType(), hit.getId()) 35 | .setSource(hit.getSourceRef(), JSON) 36 | .request(); 37 | 38 | request.get().add(index); 39 | } 40 | 41 | @Override 42 | public void onEndBatch() { 43 | request.get().execute().actionGet(); 44 | } 45 | } -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/CompoundScroll.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NonNull; 6 | import lombok.experimental.FieldDefaults; 7 | import org.elasticsearch.search.SearchHit; 8 | 9 | import java.io.IOException; 10 | 11 | import static lombok.AccessLevel.PACKAGE; 12 | import static lombok.AccessLevel.PRIVATE; 13 | 14 | @AllArgsConstructor(access = PACKAGE) 15 | @FieldDefaults(level = PRIVATE, makeFinal = true) 16 | final class CompoundScroll implements DatabaseScroll { 17 | @NonNull 18 | Iterable scrolls; 19 | 20 | @Override 21 | public void onStartBatch() throws IOException { 22 | for (final DatabaseScroll scroll : scrolls) { 23 | scroll.onStartBatch(); 24 | } 25 | } 26 | 27 | @Override 28 | public void accept(final SearchHit hit) throws IOException { 29 | for (final DatabaseScroll consumer : scrolls) { 30 | consumer.accept(hit); 31 | } 32 | } 33 | 34 | @Override 35 | public void onEndBatch() throws IOException { 36 | for (final DatabaseScroll scroll : scrolls) { 37 | scroll.onEndBatch(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/ElasticScroll.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import com.jeromeloisel.db.scroll.api.DatabaseScrolling; 5 | import lombok.experimental.FieldDefaults; 6 | import org.elasticsearch.action.search.SearchRequestBuilder; 7 | import org.elasticsearch.action.search.SearchResponse; 8 | import org.elasticsearch.client.Client; 9 | import org.elasticsearch.common.unit.TimeValue; 10 | import org.elasticsearch.index.query.QueryBuilder; 11 | import org.elasticsearch.search.SearchHit; 12 | import org.elasticsearch.search.sort.FieldSortBuilder; 13 | 14 | import java.io.IOException; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | 18 | import static java.util.Objects.requireNonNull; 19 | import static lombok.AccessLevel.PRIVATE; 20 | import static org.elasticsearch.search.sort.SortBuilders.fieldSort; 21 | 22 | @FieldDefaults(level = PRIVATE, makeFinal = true) 23 | final class ElasticScroll implements DatabaseScrolling { 24 | private static final FieldSortBuilder DEFAULT_SORT = fieldSort("_doc"); 25 | 26 | Client client; 27 | SearchRequestBuilder search; 28 | AtomicReference sort; 29 | AtomicReference scrollTime; 30 | 31 | ElasticScroll( 32 | final Client client, 33 | final SearchRequestBuilder search, 34 | final TimeValue timeValue) { 35 | super(); 36 | this.client = requireNonNull(client); 37 | this.search = requireNonNull(search); 38 | this.sort = new AtomicReference<>(DEFAULT_SORT); 39 | this.scrollTime = new AtomicReference<>(requireNonNull(timeValue)); 40 | } 41 | 42 | @Override 43 | public DatabaseScrolling withSort(final FieldSortBuilder s) { 44 | this.sort.set(requireNonNull(s)); 45 | return this; 46 | } 47 | 48 | @Override 49 | public DatabaseScrolling withScrollSize(final int size) { 50 | search.setSize(size); 51 | return this; 52 | } 53 | 54 | @Override 55 | public DatabaseScrolling withKeepAlive(final long time, final TimeUnit unit) { 56 | scrollTime.set(TimeValue.timeValueMillis(unit.toMillis(time))); 57 | return this; 58 | } 59 | 60 | @Override 61 | public DatabaseScrolling withTypes(final String... types) { 62 | search.setTypes(types); 63 | return this; 64 | } 65 | 66 | @Override 67 | public DatabaseScrolling withFetchSource(final boolean fetchSource) { 68 | search.setFetchSource(fetchSource); 69 | return this; 70 | } 71 | 72 | @Override 73 | public DatabaseScrolling withQuery(final QueryBuilder query) { 74 | search.setQuery(query); 75 | return this; 76 | } 77 | 78 | @Override 79 | public void scroll(final DatabaseScroll scroll) { 80 | SearchResponse response = search 81 | .addSort(sort.get()) 82 | .setScroll(scrollTime.get()) 83 | .execute() 84 | .actionGet(); 85 | 86 | while (true) { 87 | final String scrollId = response.getScrollId(); 88 | final SearchHit[] hits = response.getHits().getHits(); 89 | if(hits.length == 0) { 90 | break; 91 | } 92 | 93 | try { 94 | scroll.onStartBatch(); 95 | try { 96 | for (final SearchHit hit : hits) { 97 | scroll.accept(hit); 98 | } 99 | } finally { 100 | scroll.onEndBatch(); 101 | } 102 | } catch (final IOException e) { 103 | // Ignore 104 | } 105 | 106 | response = client 107 | .prepareSearchScroll(scrollId) 108 | .setScroll(scrollTime.get()) 109 | .execute() 110 | .actionGet(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/ElasticScrollingFactory.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 4 | import com.jeromeloisel.db.scroll.api.DatabaseScrolling; 5 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 6 | import lombok.AllArgsConstructor; 7 | import lombok.NonNull; 8 | import lombok.experimental.FieldDefaults; 9 | import org.elasticsearch.action.search.SearchRequestBuilder; 10 | import org.elasticsearch.client.Client; 11 | import org.elasticsearch.common.unit.TimeValue; 12 | import org.springframework.stereotype.Service; 13 | 14 | import static com.google.common.collect.ImmutableList.copyOf; 15 | import static java.util.Objects.requireNonNull; 16 | import static lombok.AccessLevel.PACKAGE; 17 | import static lombok.AccessLevel.PRIVATE; 18 | import static org.elasticsearch.common.unit.TimeValue.timeValueMinutes; 19 | 20 | @Service 21 | @AllArgsConstructor(access = PACKAGE) 22 | @FieldDefaults(level = PRIVATE, makeFinal = true) 23 | public final class ElasticScrollingFactory implements DatabaseScrollingFactory { 24 | private static final TimeValue DEFAULT_SCROLL_TIME = timeValueMinutes(1); 25 | public static final int DEFAULT_SCROLL_SIZE = 100; 26 | 27 | @NonNull 28 | Client client; 29 | 30 | @Override 31 | public DatabaseScrolling newScroll(final String index) { 32 | final SearchRequestBuilder search = client 33 | .prepareSearch(requireNonNull(index)) 34 | .setFetchSource(true) 35 | .setSize(DEFAULT_SCROLL_SIZE); 36 | return newScroll(search); 37 | } 38 | 39 | @Override 40 | public DatabaseScrolling newScroll(final SearchRequestBuilder search) { 41 | return new ElasticScroll(client, search, DEFAULT_SCROLL_TIME); 42 | } 43 | 44 | @Override 45 | public DatabaseScroll bulkDelete() { 46 | return new BulkDelete(client); 47 | } 48 | 49 | @Override 50 | public DatabaseScroll bulkIndex() { 51 | return new BulkIndex(client); 52 | } 53 | 54 | @Override 55 | public DatabaseScroll combine(final DatabaseScroll... scrolls) { 56 | return new CompoundScroll(copyOf(scrolls)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/main/java/com/jeromeloisel/database/scroll/elastic/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Elasticsearch Database Scrolling. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.database.scroll.elastic; 8 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/BulkDeleteEsTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 4 | import com.jeromeloisel.db.conversion.jackson.JacksonSerializationFactory; 5 | import com.jeromeloisel.db.integration.test.SpringElasticSearchTest; 6 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 7 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 8 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 9 | import org.elasticsearch.action.index.IndexRequest; 10 | import org.elasticsearch.client.IndicesAdminClient; 11 | import org.junit.After; 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.Mockito; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | 18 | import java.io.IOException; 19 | import java.util.UUID; 20 | import java.util.concurrent.atomic.AtomicLong; 21 | 22 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 23 | import static org.mockito.Mockito.doThrow; 24 | 25 | public class BulkDeleteEsTest extends SpringElasticSearchTest { 26 | private static final String INDEX = "data"; 27 | private static final String TYPE = "person"; 28 | private static final int SIZE = ElasticScrollingFactory.DEFAULT_SCROLL_SIZE + 1; 29 | 30 | @Autowired 31 | private JacksonSerializationFactory mapper; 32 | @Autowired 33 | private DatabaseScrollingFactory scroll; 34 | 35 | @Before 36 | public void before() throws IOException { 37 | if(!client.admin().indices().prepareExists(INDEX).execute().actionGet().isExists()) { 38 | client.admin().indices().prepareCreate(INDEX).execute().actionGet(); 39 | } 40 | final BulkRequestBuilder bulk = client.prepareBulk(); 41 | final JsonSerializer serializer = mapper.serializer(Person.class); 42 | for (int i = 0; i < SIZE; i++) { 43 | 44 | final String name = UUID.randomUUID().toString(); 45 | final IndexRequest request = new IndexRequest(INDEX, TYPE); 46 | request.source(serializer.apply(Person.builder().id("").firstname(name).lastname(name).build()), JSON); 47 | bulk.add(request); 48 | } 49 | 50 | client.bulk(bulk.request()).actionGet(); 51 | flush(INDEX); 52 | } 53 | 54 | @Test 55 | public void shouldScroll() { 56 | scroll.newScroll(INDEX).scroll(scroll.bulkDelete()); 57 | flush(INDEX); 58 | final AtomicLong atomic = new AtomicLong(); 59 | scroll.newScroll(INDEX).scroll(hit -> atomic.incrementAndGet()); 60 | Assert.assertEquals( 61 | 0L, atomic.longValue()); 62 | } 63 | 64 | @Test 65 | public void shouldNotScroll() throws IOException { 66 | final DatabaseScroll mock = Mockito.mock(DatabaseScroll.class); 67 | doThrow(new IOException("")).when(mock).onStartBatch(); 68 | scroll.newScroll(INDEX).scroll(mock); 69 | } 70 | 71 | @After 72 | public void after() { 73 | final IndicesAdminClient indices = client.admin().indices(); 74 | if(indices.prepareExists(INDEX).execute().actionGet().isExists()) { 75 | indices.prepareDelete(INDEX).execute().actionGet(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/BulkDeleteTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | 4 | import com.google.common.testing.NullPointerTester; 5 | import org.junit.Test; 6 | 7 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 8 | 9 | public class BulkDeleteTest { 10 | 11 | @Test 12 | public void shouldPassNPETester() { 13 | new NullPointerTester().testConstructors(BulkDelete.class, PACKAGE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/BulkIndexEsTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 4 | import com.jeromeloisel.db.conversion.jackson.JacksonSerializationFactory; 5 | import com.jeromeloisel.db.integration.test.SpringElasticSearchTest; 6 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 7 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 8 | import org.elasticsearch.action.index.IndexRequest; 9 | import org.elasticsearch.client.IndicesAdminClient; 10 | import org.junit.After; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | import java.io.IOException; 17 | import java.util.UUID; 18 | import java.util.concurrent.atomic.AtomicLong; 19 | 20 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 21 | 22 | public class BulkIndexEsTest extends SpringElasticSearchTest { 23 | private static final String INDEX = "data"; 24 | private static final String TYPE = "person"; 25 | private static final long SIZE = ElasticScrollingFactory.DEFAULT_SCROLL_SIZE + 1; 26 | 27 | @Autowired 28 | private JacksonSerializationFactory mapper; 29 | @Autowired 30 | private DatabaseScrollingFactory scroll; 31 | 32 | @Before 33 | public void before() throws IOException { 34 | final IndicesAdminClient indices = client.admin().indices(); 35 | if(!indices.prepareExists(INDEX).execute().actionGet().isExists()) { 36 | indices.prepareCreate(INDEX).execute().actionGet(); 37 | } 38 | final JsonSerializer serializer = mapper.serializer(Person.class); 39 | final BulkRequestBuilder bulk = client.prepareBulk(); 40 | for (int i = 0; i < SIZE; i++) { 41 | 42 | final String name = UUID.randomUUID().toString(); 43 | final IndexRequest request = new IndexRequest(INDEX, TYPE); 44 | request.source(serializer.apply(Person.builder().id("").firstname(name).lastname(name).build()), JSON); 45 | bulk.add(request); 46 | } 47 | 48 | client.bulk(bulk.request()).actionGet(); 49 | flush(INDEX); 50 | } 51 | 52 | @Test 53 | public void shouldScroll() { 54 | scroll.newScroll(INDEX).scroll(scroll.bulkIndex()); 55 | flush(INDEX); 56 | final AtomicLong atomic = new AtomicLong(); 57 | scroll.newScroll(INDEX).scroll(hit -> { 58 | atomic.incrementAndGet(); 59 | }); 60 | Assert.assertEquals( 61 | SIZE, atomic.longValue()); 62 | } 63 | 64 | @After 65 | public void after() { 66 | final IndicesAdminClient indices = client.admin().indices(); 67 | if(indices.prepareExists(INDEX).execute().actionGet().isExists()) { 68 | indices.prepareDelete(INDEX).execute().actionGet(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/BulkIndexTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | 4 | import com.google.common.testing.NullPointerTester; 5 | import org.junit.Test; 6 | 7 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 8 | 9 | public class BulkIndexTest { 10 | 11 | @Test 12 | public void shouldPassNPETester() { 13 | new NullPointerTester().testConstructors(BulkIndex.class, PACKAGE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/CompoundScrollTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | 4 | import com.google.common.collect.ImmutableSet; 5 | import com.google.common.testing.NullPointerTester; 6 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 7 | import org.elasticsearch.search.SearchHit; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.Mock; 12 | import org.mockito.runners.MockitoJUnitRunner; 13 | 14 | import java.io.IOException; 15 | 16 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 17 | import static org.mockito.Mockito.verify; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class CompoundScrollTest { 21 | 22 | @Mock 23 | DatabaseScroll first; 24 | @Mock 25 | DatabaseScroll second; 26 | @Mock 27 | SearchHit hit; 28 | 29 | CompoundScroll scroll; 30 | 31 | @Before 32 | public void before() { 33 | scroll = new CompoundScroll(ImmutableSet.of(first, second)); 34 | } 35 | 36 | @Test 37 | public void shouldOnStart() throws IOException { 38 | scroll.onStartBatch(); 39 | verify(first).onStartBatch(); 40 | verify(second).onStartBatch(); 41 | } 42 | 43 | @Test 44 | public void shouldOnEnd() throws IOException { 45 | scroll.onEndBatch(); 46 | verify(first).onEndBatch(); 47 | verify(second).onEndBatch(); 48 | } 49 | 50 | @Test 51 | public void shouldAccept() throws IOException { 52 | final CompoundScroll scroll = new CompoundScroll(ImmutableSet.of(first, second)); 53 | scroll.accept(hit); 54 | verify(first).accept(hit); 55 | verify(second).accept(hit); 56 | } 57 | 58 | @Test 59 | public void shouldPassNPETester() { 60 | new NullPointerTester().testConstructors(CompoundScroll.class, PACKAGE); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/ElasticScrollTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.google.common.testing.NullPointerTester; 4 | import org.elasticsearch.action.search.SearchRequestBuilder; 5 | import org.elasticsearch.client.Client; 6 | import org.elasticsearch.common.unit.TimeValue; 7 | import org.elasticsearch.index.query.MatchAllQueryBuilder; 8 | import org.elasticsearch.index.query.QueryBuilder; 9 | import org.elasticsearch.search.sort.FieldSortBuilder; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.Mock; 14 | import org.mockito.runners.MockitoJUnitRunner; 15 | 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class ElasticScrollTest { 24 | 25 | @Mock 26 | Client client; 27 | @Mock 28 | SearchRequestBuilder search; 29 | 30 | private ElasticScroll scroll; 31 | 32 | @Before 33 | public void before() { 34 | scroll = new ElasticScroll(client, search, TimeValue.ZERO); 35 | } 36 | 37 | @Test 38 | public void shouldFetchSource() { 39 | scroll.withFetchSource(true); 40 | verify(search).setFetchSource(true); 41 | } 42 | 43 | @Test 44 | public void shouldWithKeepAlive() { 45 | scroll.withKeepAlive(1, TimeUnit.SECONDS); 46 | } 47 | 48 | @Test 49 | public void shouldWithQuery() { 50 | final QueryBuilder query = new MatchAllQueryBuilder(); 51 | scroll.withQuery(query); 52 | verify(search).setQuery(query); 53 | } 54 | 55 | @Test 56 | public void shouldWithSort() { 57 | final FieldSortBuilder sort = new FieldSortBuilder("_doc"); 58 | scroll.withSort(sort); 59 | } 60 | 61 | @Test 62 | public void shouldWithSize() { 63 | scroll.withScrollSize(123); 64 | verify(search).setSize(123); 65 | } 66 | 67 | @Test 68 | public void shoudlWithTypes() { 69 | scroll.withTypes("type"); 70 | verify(search).setTypes("type"); 71 | } 72 | 73 | @Test 74 | public void shouldPassNPETester() { 75 | new NullPointerTester() 76 | .setDefault(SearchRequestBuilder.class, mock(SearchRequestBuilder.class)) 77 | .testConstructors(ElasticScroll.class, PACKAGE); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/ElasticScrollingFactoryEsTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonSerializer; 4 | import com.jeromeloisel.db.conversion.jackson.JacksonSerializationFactory; 5 | import com.jeromeloisel.db.integration.test.SpringElasticSearchTest; 6 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 7 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 8 | import org.elasticsearch.action.index.IndexRequest; 9 | import org.elasticsearch.client.IndicesAdminClient; 10 | import org.junit.After; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | import java.io.IOException; 17 | import java.util.UUID; 18 | import java.util.concurrent.atomic.AtomicLong; 19 | 20 | import static org.elasticsearch.common.xcontent.XContentType.JSON; 21 | 22 | public class ElasticScrollingFactoryEsTest extends SpringElasticSearchTest { 23 | private static final String INDEX = "data"; 24 | private static final String TYPE = "person"; 25 | private static final int SIZE = ElasticScrollingFactory.DEFAULT_SCROLL_SIZE + 1; 26 | 27 | @Autowired 28 | private JacksonSerializationFactory mapper; 29 | @Autowired 30 | private DatabaseScrollingFactory scroll; 31 | 32 | @Before 33 | public void before() throws IOException { 34 | if(!client.admin().indices().prepareExists(INDEX).execute().actionGet().isExists()) { 35 | client.admin().indices().prepareCreate(INDEX).execute().actionGet(); 36 | } 37 | final JsonSerializer serializer = mapper.serializer(Person.class); 38 | final BulkRequestBuilder bulk = client.prepareBulk(); 39 | for (int i = 0; i < SIZE; i++) { 40 | 41 | final String name = UUID.randomUUID().toString(); 42 | final IndexRequest request = new IndexRequest(INDEX, TYPE); 43 | request.source(serializer.apply(Person.builder().id("").firstname(name).lastname(name).build()), JSON); 44 | bulk.add(request); 45 | } 46 | 47 | client.bulk(bulk.request()).actionGet(); 48 | flush(INDEX); 49 | } 50 | 51 | @Test 52 | public void shouldScroll() { 53 | final AtomicLong atomic = new AtomicLong(); 54 | scroll.newScroll(INDEX).scroll(hit -> atomic.incrementAndGet()); 55 | Assert.assertEquals( 56 | SIZE, atomic.longValue()); 57 | } 58 | 59 | @After 60 | public void after() { 61 | final IndicesAdminClient indices = client.admin().indices(); 62 | if(indices.prepareExists(INDEX).execute().actionGet().isExists()) { 63 | indices.prepareDelete(INDEX).execute().actionGet(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/ElasticScrollingFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.google.common.testing.NullPointerTester; 4 | import com.jeromeloisel.db.scroll.api.DatabaseScroll; 5 | import org.elasticsearch.client.Client; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static com.google.common.testing.NullPointerTester.Visibility.PACKAGE; 12 | import static org.junit.Assert.assertEquals; 13 | 14 | @RunWith(MockitoJUnitRunner.class) 15 | public class ElasticScrollingFactoryTest { 16 | 17 | @Mock 18 | Client client; 19 | @Mock 20 | DatabaseScroll scroll; 21 | 22 | @Test 23 | public void shouldCombine() { 24 | final ElasticScrollingFactory factory = new ElasticScrollingFactory(client); 25 | final DatabaseScroll combine = factory.combine(scroll); 26 | assertEquals(CompoundScroll.class, combine.getClass()); 27 | } 28 | 29 | @Test 30 | public void shouldPassNPETester() { 31 | new NullPointerTester().testConstructors(ElasticScrollingFactory.class, PACKAGE); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /db-scroll-elastic/src/test/java/com/jeromeloisel/database/scroll/elastic/Person.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.database.scroll.elastic; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.jeromeloisel.db.entity.Entity; 6 | import lombok.Builder; 7 | import lombok.Value; 8 | import lombok.experimental.Wither; 9 | 10 | import static com.google.common.base.Preconditions.checkNotNull; 11 | 12 | @Value 13 | @Builder 14 | public class Person implements Entity { 15 | @Wither 16 | String id; 17 | String firstname; 18 | String lastname; 19 | 20 | @JsonCreator 21 | Person( 22 | @JsonProperty("id") final String id, 23 | @JsonProperty("firstname") final String firstname, 24 | @JsonProperty("lastname") final String lastname) { 25 | super(); 26 | this.id = checkNotNull(id); 27 | this.firstname = checkNotNull(firstname); 28 | this.lastname = checkNotNull(lastname); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jeromeloisel 6 | elasticsearch-crud 7 | 5.6.4-SNAPSHOT 8 | 9 | db-spring-elasticsearch-starter 10 | Spring Boot Elasticsearch DB starter 11 | ElasticSearch Repository Spring Boot autoconfiguration with Jackson serialization. 12 | 13 | 14 | 15 | com.jeromeloisel 16 | db-conversion-jackson 17 | ${project.version} 18 | 19 | 20 | com.jeromeloisel 21 | db-repository-elasticsearch 22 | ${project.version} 23 | 24 | 25 | com.jeromeloisel 26 | db-scroll-elastic 27 | ${project.version} 28 | 29 | 30 | com.fasterxml.jackson.core 31 | jackson-databind 32 | true 33 | 34 | 35 | org.springframework 36 | spring-context 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-autoconfigure 41 | ${spring.boot.version} 42 | 43 | 44 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/main/java/com/jeromeloisel/repository/elasticsearch/starter/ElasticSearchRepositoryFactoryAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.repository.elasticsearch.starter; 2 | 3 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 4 | import com.jeromeloisel.db.repository.api.DatabaseRepositoryFactory; 5 | import com.jeromeloisel.db.repository.elasticsearch.ElasticSearchRepositoryFactory; 6 | import com.jeromeloisel.db.scroll.api.DatabaseScrollingFactory; 7 | import org.elasticsearch.client.Client; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | /** 14 | * Elasticsearch Repository autoconfiguration. 15 | * 16 | * @author jerome 17 | * 18 | */ 19 | @Configuration 20 | public class ElasticSearchRepositoryFactoryAutoConfiguration { 21 | 22 | @Bean 23 | @ConditionalOnMissingBean(DatabaseRepositoryFactory.class) 24 | @ConditionalOnBean(value={Client.class, JsonSerializationFactory.class, DatabaseScrollingFactory.class}) 25 | DatabaseRepositoryFactory databaseRepositoryFactory( 26 | final Client client, 27 | final JsonSerializationFactory serialization, 28 | final DatabaseScrollingFactory factory) { 29 | return new ElasticSearchRepositoryFactory(serialization, client, factory); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/main/java/com/jeromeloisel/repository/elasticsearch/starter/JacksonConversionAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.repository.elasticsearch.starter; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.jeromeloisel.db.conversion.api.JsonSerializationFactory; 12 | import com.jeromeloisel.db.conversion.jackson.JacksonSerializationFactory; 13 | 14 | /** 15 | * Jackson Json Conversion autoconfiguration. 16 | * 17 | * @author jerome 18 | * 19 | */ 20 | @Configuration 21 | @ConditionalOnClass(ObjectMapper.class) 22 | public class JacksonConversionAutoConfiguration { 23 | 24 | @Bean 25 | @Autowired 26 | @ConditionalOnBean(ObjectMapper.class) 27 | @ConditionalOnMissingBean(JsonSerializationFactory.class) 28 | JsonSerializationFactory jsonSerializationFactory(final ObjectMapper mapper) { 29 | return new JacksonSerializationFactory(mapper); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/main/java/com/jeromeloisel/repository/elasticsearch/starter/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Spring Elasticsearch DB configuration. 3 | * 4 | * @author jerome 5 | * 6 | */ 7 | package com.jeromeloisel.repository.elasticsearch.starter; 8 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/test/java/com/jeromeloisel/Application.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(final String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/test/java/com/jeromeloisel/repository/elasticsearch/starter/TestConfig.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.repository.elasticsearch.starter; 2 | 3 | import static org.mockito.Mockito.mock; 4 | 5 | import org.elasticsearch.client.Client; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | @Configuration 12 | class TestConfig { 13 | 14 | @Bean 15 | ObjectMapper objectMapper() { 16 | return new ObjectMapper(); 17 | } 18 | 19 | @Bean 20 | Client client() { 21 | return mock(Client.class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db-spring-elasticsearch-starter/src/test/java/com/jeromeloisel/repository/elasticsearch/starter/WiringTest.java: -------------------------------------------------------------------------------- 1 | package com.jeromeloisel.repository.elasticsearch.starter; 2 | 3 | import com.jeromeloisel.Application; 4 | import com.jeromeloisel.db.repository.api.DatabaseRepositoryFactory; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import static org.junit.Assert.assertNotNull; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest(classes=Application.class) 15 | public class WiringTest { 16 | 17 | @Autowired 18 | DatabaseRepositoryFactory factory; 19 | 20 | @Test 21 | public void shouldAutowire() { 22 | assertNotNull(factory); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.jcabi 6 | parent 7 | 0.49.2 8 | 9 | 10 | com.jeromeloisel 11 | elasticsearch-crud 12 | 5.6.4-SNAPSHOT 13 | pom 14 | ElasticSearch CRUD Repository 15 | Easily perform CRUD operations on Beans stored on Elasticsearch. 16 | 17 | 18 | yyyy-MM-dd'T'HH:mm:ss 19 | **/*IntegrationTest.java 20 | 21 | 2.6 22 | 5.6.3 23 | 23.2-jre 24 | 2.9.2 25 | 4.12 26 | 2.9.1 27 | 1.16.12 28 | 6.3.0 29 | 1.10.19 30 | 4.3.12.RELEASE 31 | 1.5.8.RELEASE 32 | 1.7.25 33 | 34 | 35 | 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | ${slf4j.version} 41 | 42 | 43 | org.springframework 44 | spring-context 45 | ${spring.version} 46 | 47 | 48 | org.springframework 49 | spring-test 50 | ${spring.version} 51 | test 52 | 53 | 54 | commons-io 55 | commons-io 56 | ${commons.io.version} 57 | 58 | 59 | com.fasterxml.jackson.core 60 | jackson-annotations 61 | ${jackson.version} 62 | 63 | 64 | com.fasterxml.jackson.core 65 | jackson-databind 66 | ${jackson.version} 67 | 68 | 69 | com.fasterxml.jackson.datatype 70 | jackson-datatype-jdk8 71 | ${jackson.version} 72 | 73 | 74 | org.elasticsearch 75 | elasticsearch 76 | ${elasticsearch.version} 77 | 78 | 79 | org.elasticsearch.client 80 | transport 81 | ${elasticsearch.version} 82 | 83 | 84 | org.elasticsearch.test 85 | framework 86 | ${elasticsearch.version} 87 | test 88 | 89 | 90 | 91 | com.google.guava 92 | guava-testlib 93 | ${guava.version} 94 | test 95 | 96 | 97 | org.mockito 98 | mockito-all 99 | ${mockito.version} 100 | test 101 | 102 | 103 | junit 104 | junit 105 | ${junit.version} 106 | test 107 | 108 | 109 | org.mockito 110 | mockito-core 111 | ${mockito.version} 112 | test 113 | 114 | 115 | org.apache.logging.log4j 116 | log4j-api 117 | ${log4j.version} 118 | 119 | 120 | org.apache.logging.log4j 121 | log4j-core 122 | ${log4j.version} 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.mockito 130 | mockito-all 131 | test 132 | 133 | 134 | org.projectlombok 135 | lombok 136 | provided 137 | 138 | 139 | org.springframework.boot 140 | spring-boot-starter-test 141 | ${spring.boot.version} 142 | test 143 | 144 | 145 | com.google.guava 146 | guava-testlib 147 | 148 | 149 | junit 150 | junit 151 | 152 | 153 | org.springframework 154 | spring-test 155 | ${spring.version} 156 | test 157 | 158 | 159 | com.google.guava 160 | guava 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-failsafe-plugin 169 | 2.20.1 170 | 171 | suites 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-surefire-plugin 177 | 2.20.1 178 | 179 | suites 180 | false 181 | 1 182 | 183 | file:/dev/./urandom 184 | true 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-compiler-plugin 191 | 3.7.0 192 | 193 | 1.8 194 | 1.8 195 | true 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-source-plugin 201 | 3.0.1 202 | 203 | 204 | attach-sources 205 | 206 | jar 207 | 208 | 209 | 210 | 211 | 212 | org.apache.maven.plugins 213 | maven-javadoc-plugin 214 | 2.10.4 215 | 216 | 217 | attach-javadocs 218 | 219 | jar 220 | 221 | 222 | -Xdoclint:none 223 | 224 | 225 | 226 | 227 | 228 | org.apache.maven.plugins 229 | maven-jar-plugin 230 | 3.0.2 231 | 232 | 233 | 234 | test-jar 235 | 236 | 237 | 238 | 239 | true 240 | 241 | 242 | 243 | org.eluder.coveralls 244 | coveralls-maven-plugin 245 | 4.3.0 246 | 247 | ${maven.build.timestamp.format} 248 | 249 | 250 | 251 | org.jacoco 252 | jacoco-maven-plugin 253 | 0.7.9 254 | 255 | 256 | prepare-agent 257 | 258 | prepare-agent 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | scm:git:git@github.com:jloisel/elastic-crud.git 268 | scm:git:git@github.com:jloisel/elastic-crud.git 269 | git@github.com:jloisel/elastic-crud.git 270 | 271 | 272 | 273 | 274 | Jerome Loisel 275 | loisel.jerome@gmail.com 276 | https://jeromeloisel.com 277 | 278 | 279 | 280 | 281 | 282 | Apache License 2.0 283 | http://www.spdx.org/licenses/Apache-2.0 284 | 285 | 286 | 287 | 288 | db-repository-api 289 | db-entity 290 | db-conversion-api 291 | db-conversion-jackson 292 | db-repository-elasticsearch 293 | db-spring-elasticsearch-starter 294 | db-integration-test 295 | db-scroll-api 296 | db-scroll-elastic 297 | 298 | 299 | -------------------------------------------------------------------------------- /pubring.gpg.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: GnuPG v1 3 | 4 | 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 65 | =fQ+w 66 | -----END PGP MESSAGE----- 67 | 68 | -------------------------------------------------------------------------------- /secring.gpg.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: GnuPG v1 3 | 4 | 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 70 | =Abq4 71 | -----END PGP MESSAGE----- 72 | 73 | -------------------------------------------------------------------------------- /settings.xml.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: GnuPG v1 3 | 4 | hQEMA5qETcGag5w6AQf/R2COfUM8cbBoLq/OB6L6b5btkxfJ5a3fWiKBT18+Ow2I 5 | WVhUy7yZlkANnb2k72dJuQCRRyQuqgsouESAj6dnlaUkcZBTA3wYU4N6SRkzMMik 6 | 2AmnsX7vkhITf3v0Dcg4M/0BYN4+tuJsF9quUDtaAW+slunmipMQ55dr4OKnTPjy 7 | QAmdwQTmIVqo1zByS+SJz3lMARt/+1MWAAgny2MoxKN3AhTR8k1mQGlMIrDw++q5 8 | 7/venCMtXdCMey7PbvvCWthAbgr6Sv/AjWWNlBS8fXjaFoA9holONd5FWRK1UuB2 9 | 3Bjmued3220+BpVIvNWBvadWz96G6bdOO6BsvC47HNLpAb6Bgry9wghGdJjT1Aw3 10 | 6M8Ez9RPG7XJ2o3d2scxTv21pu+z0mze08HSVQeWByfLrIY6TkBA9BgTiG2kccUS 11 | j/cONqaO3sS9/SxOnUNtkIXa6oThj5bG0x6+9/FdTq5S+qoro1zwKrgTl629il3r 12 | k0B32nk+fOM3xdGbXu6ZFrNAMMRdOkq5fSExRtNc157EM8oXoDtWGc/CHjIjLsJo 13 | 1R0sV3558G1yDBLjPEaMPaM7coVN+Ylm0X+TtEOfRv/bu7cvGvRiAsMf0xJv6Osu 14 | kgqeHGNeGF3IqQEh9k8oXdhWLJifjZc24Dep1iuOTkrKuZbPa9/usQtOwKEicsDj 15 | DTQubx1aiVIXldpN1UB5IOkXOKgN5wq0qigtoS/+tlcY98IGsloz7m6foED0axO7 16 | RO4HvODshep2Hix3G5XYxAFoKUeTbXGwLNq7a+84FdOu2Nmo+6+6gxHfvjKPDrvK 17 | rtNIql+ju2lhcOd2QWIXy+bkVbjDyHDIO5gvc5imQuy9kj8+yMIdIQ3tl7vnNcpc 18 | 4WRNF56K6134k3GvFPNPOn4wNunHZ0kXbH1/O93/4WoI9+6+MV6dxS+LGKwn9oa1 19 | EFyhVNVCAgdCQoT4YJ4Ty45/i0pMLGeUkT07qzvoQ4B+N9q+Y9LSu7Ymj8leZlVm 20 | cQd86CupxG7XKjcyD2HJOVwM5sNlD1eINd5GoPAE 21 | =VVe2 22 | -----END PGP MESSAGE----- 23 | --------------------------------------------------------------------------------