├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build.gradle ├── buildSrc └── settings.gradle ├── docs ├── configuring-actionrequests.asciidoc ├── enhanced-closures.asciidoc ├── overview.asciidoc └── quickstart.asciidoc ├── gradle.properties ├── settings.gradle └── src ├── main ├── groovy │ └── org │ │ └── elasticsearch │ │ └── groovy │ │ ├── ClosureExtensions.groovy │ │ ├── ClosureToMapConverter.groovy │ │ ├── action │ │ ├── ListenableActionFutureExtensions.groovy │ │ ├── admin │ │ │ ├── cluster │ │ │ │ ├── repositories │ │ │ │ │ └── put │ │ │ │ │ │ └── PutRepositoryRequestExtensions.groovy │ │ │ │ ├── settings │ │ │ │ │ └── ClusterUpdateSettingsRequestExtensions.groovy │ │ │ │ ├── snapshots │ │ │ │ │ ├── create │ │ │ │ │ │ └── CreateSnapshotRequestExtensions.groovy │ │ │ │ │ └── restore │ │ │ │ │ │ └── RestoreSnapshotRequestExtensions.groovy │ │ │ │ └── storedscripts │ │ │ │ │ └── PutStoredScriptRequestExtensions.groovy │ │ │ └── indices │ │ │ │ ├── create │ │ │ │ └── CreateIndexRequestExtensions.groovy │ │ │ │ ├── mapping │ │ │ │ └── put │ │ │ │ │ └── PutMappingRequestExtensions.groovy │ │ │ │ └── settings │ │ │ │ └── put │ │ │ │ └── UpdateSettingsRequestExtensions.groovy │ │ ├── explain │ │ │ └── ExplainRequestExtensions.groovy │ │ ├── fieldstats │ │ │ └── FieldStatsRequestExtensions.groovy │ │ ├── index │ │ │ └── IndexRequestExtensions.groovy │ │ ├── search │ │ │ └── SearchRequestExtensions.groovy │ │ └── update │ │ │ └── UpdateRequestExtensions.groovy │ │ ├── client │ │ ├── AbstractClientExtensions.groovy │ │ ├── AdminClientExtensions.groovy │ │ ├── ClientExtensions.groovy │ │ ├── ClusterAdminClientExtensions.groovy │ │ └── IndicesAdminClientExtensions.groovy │ │ └── common │ │ ├── settings │ │ ├── SettingsBuilderExtensions.groovy │ │ └── SettingsStaticExtensions.groovy │ │ └── xcontent │ │ └── XContentBuilderExtensions.groovy └── resources │ └── META-INF │ └── services │ └── org.codehaus.groovy.runtime.ExtensionModule └── test ├── groovy └── org │ └── elasticsearch │ └── groovy │ ├── AbstractESIntegTestCase.groovy │ ├── AbstractESTestCase.groovy │ ├── ClosureExtensionsTests.groovy │ ├── ClosureToMapConverterTests.groovy │ ├── GroovyTestSanitizer.groovy │ ├── action │ └── ListenableActionFutureExtensionsTests.groovy │ ├── client │ ├── AbstractClientTests.groovy │ ├── AdminClientExtensionsTests.groovy │ ├── ClientExtensionsActionTests.groovy │ ├── ClientExtensionsTests.groovy │ ├── ClusterAdminClientExtensionsActionTests.groovy │ └── IndicesAdminClientExtensionsActionTests.groovy │ └── common │ ├── settings │ ├── SettingsBuilderExtensionsTests.groovy │ └── SettingsStaticExtensionsTests.groovy │ └── xcontent │ └── XContentBuilderExtensionsTests.groovy └── resources └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /data 3 | /work 4 | /logs 5 | /.idea 6 | /target 7 | .DS_Store 8 | *.iml 9 | /buildSrc/.gradle 10 | /.gradle 11 | /.project 12 | /.classpath 13 | /.settings 14 | .local-execution-hints.log 15 | /libs 16 | 17 | secrets.* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Elasticsearch Groovy Client 2 | =========================================== 3 | 4 | Elasticsearch is an open source project and we love to receive contributions from our community — you! 5 | There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, 6 | submitting bug reports and feature requests or writing code which can be incorporated into the 7 | Elasticsearch Groovy client itself. 8 | 9 | Bug Reports 10 | ----------- 11 | 12 | If you think you have found a bug in the Elasticsearch Groovy client, first make sure that you are 13 | testing against the [latest version of Elasticsearch](http://www.elasticsearch.org/download/) and the 14 | Groovy client - your issue may already have been fixed. If not, search our 15 | [issues list](https://github.com/elasticsearch/elasticsearch-groovy/issues) on GitHub in case a similar 16 | issue has already been opened. Do feel free to add to existing issues, but please consider that practically 17 | everyone has different priorities. 18 | 19 | It is very helpful if you can prepare a reproduction of the bug. In other words, provide a small test case which we can 20 | run to confirm your bug. It makes it easier to find the problem and to fix it. Test cases should be provided as `curl` 21 | commands which we can copy and paste to run it locally, for example: 22 | 23 | ```groovy 24 | // Using a node client 25 | Client client = nodeBuilder().local(true).node() 26 | 27 | // 1. delete the index 28 | client.admin.indices.delete { 29 | indices "test" 30 | }.actionGet() 31 | 32 | // 2. insert a document 33 | client.index { 34 | index "test" 35 | type "test" 36 | id "1" 37 | source { 38 | title = "test document" 39 | } 40 | } 41 | 42 | // 3. this should return XXXX but instead returns YYY 43 | client.... 44 | ``` 45 | 46 | Provide as much information as you can. You may think that the problem lies with your query, when actually it depends 47 | on how your data is indexed. The easier it is for us to recreate your problem, the faster it is likely to be fixed. 48 | 49 | Feature Requests 50 | ---------------- 51 | 52 | If you find yourself wishing for a feature that doesn't exist in the Elasticsearch Groovy client, you are probably not 53 | alone. There are bound to be others out there with similar needs. 54 | 55 | Many of the features that [Elasticsearch](https://github.com/elasticsearch/elasticsearch) has today have been added 56 | because our users saw the need. Open an issue on our [issues 57 | list](https://github.com/elasticsearch/elasticsearch-groovy/issues) on GitHub which describes the feature you would 58 | like to see, why you need it, and how it should work. 59 | 60 | Contributing Code and Documentation Changes 61 | ------------------------------------------- 62 | 63 | If you have a bugfix or new feature that you would like to contribute to the Elasticsearch Groovy client, please find 64 | or open an issue about it _first_. Talk about what you would like to do. It may be that somebody is already working on 65 | it or that there are particular issues that you should know about before implementing the change. 66 | 67 | We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is 68 | important to find the best approach before writing too much code. 69 | 70 | The process for contributing to any of the [Elasticsearch repositories](https://github.com/elasticsearch/) is similar. 71 | Details for individual projects can be found below. 72 | 73 | ### Fork and Clone the Repository 74 | 75 | You will need to fork the main Elasticsearch code or documentation repository and clone it to your local machine. See 76 | [GitHub help page](https://help.github.com/articles/fork-a-repo) for help. 77 | 78 | Further instructions for specific projects are given below. 79 | 80 | ### Submitting Your Changes 81 | 82 | Once your changes and tests are ready to submit for review: 83 | 84 | 1. Test your changes 85 | 86 | Run the test suite to make sure that nothing is broken. 87 | 88 | 2. Sign the Contributor License Agreement 89 | 90 | Please make sure you have signed our [Contributor License 91 | Agreement](http://www.elasticsearch.org/contributor-agreement/). We are not asking you to assign copyright to us, 92 | but to give us the right to distribute your code without restriction. We ask this of all contributors in order to 93 | assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. 94 | 95 | 3. Rebase your changes 96 | 97 | Update your local repository with the most recent code from the main Elasticsearch Groovy repository, and rebase 98 | your branch on top of the latest master branch. We prefer your changes to be squashed into a single commit. 99 | 100 | Note: This can be done as the last step and you can rebase as the final step before a merge is accepted in order to 101 | make it easier to follow along during the active development and review portion of a pull request. 102 | 103 | 4. Submit a pull request 104 | 105 | Push your local changes to your forked copy of the repository and 106 | [submit a pull request](https://help.github.com/articles/using-pull-requests). In the pull request, describe what 107 | your changes do and mention the number of the issue where discussion has taken place, eg "Closes #123". 108 | 109 | Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, we 110 | would love to work with you to get your pull request merged into the Elasticsearch Groovy client. 111 | 112 | Contributing to the Elasticsearch Groovy client 113 | ----------------------------------------------- 114 | 115 | **Repository:** [https://github.com/elasticsearch/elasticsearch-groovy](https://github.com/elasticsearch/elasticsearch-groovy) 116 | 117 | Make sure you have [Gradle](http://gradle.org) installed, as Elasticsearch uses it as its build system. Integration with IntelliJ and Eclipse should work out of the box thanks to Gradle. 118 | 119 | * The build was tested with Gradle 2.1, but earlier versions will _probably_ work. 120 | 121 | Please follow these formatting guidelines: 122 | 123 | * Java indent is 4 spaces 124 | * Line width is 140 characters 125 | * The rest is left to Java coding standards 126 | * Disable “auto-format on save” to prevent unnecessary format changes. This makes reviews much harder as it generates unnecessary formatting changes. If your IDE supports formatting only modified chunks that is fine to do. 127 | 128 | To create a distribution from the source without running any tests, simply run: 129 | 130 | ```sh 131 | $ cd elasticsearch-groovy/ 132 | $ gradle clean installDist 133 | ``` 134 | 135 | You will find the newly built packages under: `./build/install/elasticsearch-groovy`. 136 | 137 | Before submitting your changes, run the test suite to make sure that nothing is broken, with: 138 | 139 | ```sh 140 | $ gradle clean test 141 | ``` 142 | 143 | Any errors can be more easily interpreted by reading the generated error reports shown by 144 | `./build/reports/tests/index.html`. 145 | 146 | The Elasticsearch Groovy client reuses the Elasticsearch and Lucene test frameworks, which both expect Maven to be used 147 | as the build tool. As a result, any test errors should display messages such as (under the Standard output tab within 148 | the reports) 149 | 150 | > REPRODUCE WITH : mvn clean test -Dtests.seed=887C4F14E262EDD3 151 | > -Dtests.class=org.elasticsearch.groovy.common.settings.ImmutableSettingsBuilderExtensionsTests -Dtests.prefix=tests 152 | > -Dfile.encoding=UTF-8 -Duser.timezone=America/New_York -Dtests.method="testExtensionModuleConfigured" 153 | > -Dtests.processors=4 154 | 155 | The above line _can_ be run by changing `mvn` to `gradle`. The rest can stay the exact same. 156 | 157 | In order to support the test framework's functionality, all test classes must either extend `ElasticsearchTestCase` or 158 | `ElasticsearchIntegrationTest`. 159 | 160 | Source: [Contributing to elasticsearch](http://www.elasticsearch.org/contributing-to-elasticsearch/) 161 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Groovy Client for Elasticsearch 2 | =============================== 3 | 4 | **IMPORTANT**: The Groovy Client is deprecated as of Elasticsearch v6.0.0 and is no longer actively maintained. 5 | 6 | The Elasticsearch Groovy client project helps you to use Elasticsearch in Groovy projects. This Groovy client inherently 7 | supports 100% of the Elasticsearch API for the supported version by using Groovy extension modules with the Java client. 8 | Literally anything possible in the same version of the Java client is possible with the Groovy client, plus some 9 | Groovy-friendly extensions. 10 | 11 | You can use the Java client code from any Java client example that you find online _with the benefit of the Groovy 12 | extensions_. 13 | 14 | ```groovy 15 | TransportClient client = TransportClient.builder().settings(Settings.settingsBuilder { 16 | client.transport.sniff = true 17 | cluster.name = "your-cluster-name" 18 | }).build() 19 | 20 | // identical to the Java client: 21 | client.addTransportAddress( ... ) 22 | 23 | String userId = "some-user-id" 24 | 25 | // asynchronously fetch the results 26 | ListenableActionFuture future = client.searchAsync { 27 | indices "your-index" 28 | types "your-type" 29 | source { 30 | query { 31 | match { 32 | user.id = userId 33 | } 34 | } 35 | } 36 | } 37 | 38 | // block until the response is retrieved (you could alternatively use listeners) 39 | SearchResponse response = future.actionGet() 40 | ``` 41 | 42 | Besides the usage of `Closure`s, the above example should look very familiar to any existing Java client users, as well 43 | as those familiar with the Elasticsearch DSL (Domain Specific Language used for indexing and querying). 44 | 45 | Versions 46 | -------- 47 | 48 | You need to install a version matching your Elasticsearch version: 49 | 50 | | Elasticsearch | Groovy Client | Java | Groovy | 51 | |---------------------|-----------------------------|---------------|--------| 52 | | [master [5.0]](https://github.com/elastic/elasticsearch) | Build from source [5.0] | 8 or later | 2.4.5 | 53 | | [2.1 - 2.4](https://github.com/elastic/elasticsearch/tree/2.1) | [2.1](https://github.com/elastic/elasticsearch-groovy/tree/2.1) | 7u60 or later | 2.4.4 | 54 | | [2.0](https://github.com/elastic/elasticsearch/tree/2.0) | [2.0](https://github.com/elastic/elasticsearch-groovy/tree/2.0) | 7u60 or later | 2.4.4 | 55 | | [1.7](https://github.com/elastic/elasticsearch/tree/1.7) | [1.7](https://github.com/elastic/elasticsearch-groovy/tree/1.7) | 7u60 or later | 2.4.4 | 56 | | [1.6](https://github.com/elastic/elasticsearch/tree/1.6) | [1.6](https://github.com/elastic/elasticsearch-groovy/tree/1.6) | 7u60 or later | 2.4.4 | 57 | | [1.5](https://github.com/elastic/elasticsearch/tree/1.5) | [1.5](https://github.com/elastic/elasticsearch-groovy/tree/1.5) | 7u60 or later | _2.4.1_[*](https://github.com/elastic/elasticsearch-groovy#groovy-warning) | 58 | | [1.4](https://github.com/elastic/elasticsearch/tree/1.4) | [1.4](https://github.com/elastic/elasticsearch-groovy/tree/1.4) | 7u60 or later | _2.3.7_[*](https://github.com/elastic/elasticsearch-groovy#groovy-warning) | 59 | 60 | Please read documentation relative to the version that you are using! 61 | 62 | To build a SNAPSHOT version, you need to build it with Gradle (see below for further details): 63 | 64 | ```bash 65 | $ gradle clean installDist 66 | ``` 67 | 68 | Groovy Warning 69 | -------------- 70 | 71 | Groovy released Groovy 2.4.4 to fix a vulnerability with [CVE-2015-3253](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3253). 72 | 73 | You are considered vulnerable just by having an earlier version of Groovy on your classpath! All users should upgrade 74 | to Groovy 2.4.4, or later, as a result. 75 | 76 | JVM Warning 77 | ----------- 78 | 79 | Both Elasticsearch and the Elasticsearch Groovy client require at least Java 7. In addition, the Groovy client is 80 | compiled with `indy` support enabled, which means that it theoretically could cause issues if you are running with Java 81 | 7u22 to 7u55 due to a bug in the JVM related to `invokedynamic`. This is [reported directly from the Groovy 82 | developers](http://groovy.codehaus.org/InvokeDynamic+support) and it is strongly suggested that you run Java 7u60 or 83 | later. 84 | 85 | | JVM Release | Groovy Client Support | `invokedynamic` Support | 86 | |--------------------|-----------------------------|-------------------------| 87 | | Java 5 | *Unsupported* | None | 88 | | Java 6 | *Unsupported* | None | 89 | | Java 7u22 to 7u55 | *Unsupported* | **Buggy** | 90 | | Java 7u60 or later | Supported [1.x, 2.x] | Supported | 91 | | Java 8 | Supported [1.x or later] | Supported | 92 | 93 | Groovy is supported on any JDK supported by Elasticsearch, which currently includes Oracle JDK and OpenJDK. 94 | 95 | Adding to your Groovy projects 96 | ------------------------------ 97 | 98 | ### Gradle 99 | 100 | ```gradle 101 | repositories { 102 | mavenCentral() 103 | } 104 | 105 | dependencies { 106 | compile 'org.elasticsearch:elasticsearch-groovy:2.1.2' 107 | } 108 | ``` 109 | 110 | ### Maven 111 | 112 | ```xml 113 | 114 | 115 | org.elasticsearch 116 | elasticsearch-groovy 117 | 2.1.2 118 | compile 119 | 120 | 121 | ``` 122 | 123 | ### Grails 2.x 124 | 125 | Out-of-the-box support for Grails is limited to Grails 2.4.4 or later. To use with earlier versions, 126 | [you must follow the instructions found in GRAILS-10652](https://jira.grails.org/browse/GRAILS-10652) 127 | to load Groovy extension modules. 128 | 129 | ```gradle 130 | repositories { 131 | mavenCentral() 132 | } 133 | 134 | dependencies { 135 | // You may be able to use the 'runtime' scope 136 | compile group: 'org.elasticsearch', name: 'elasticsearch-groovy', version: '2.1.2', classifier: 'grails' 137 | } 138 | ``` 139 | 140 | Grails 3.x has overhauled their build system to use Gradle, which makes it easy for you to select the 141 | version of Groovy to use with it, including the use of `invokedynamic`. However, Grails 2.x did not 142 | make it easy. A part of not being easy, Grails 2.x does not use the `invokedynamic`-compatible Groovy 143 | jar, which means that any Grails 2.x project requires a jar that is not compiled with `invokedynamic`. 144 | 145 | With the release of Elasticsearch Groovy 1.4.3, we have introduced a secondary jar with a new `grails` 146 | _classifier_ that can be used by Grails users. All other users are _strongly_ recommended to use 147 | the `invokedynamic`-compatible versions described above (it's both faster and slightly smaller!). 148 | 149 | Support for this is intended to assist the Grails community to use the Elasticsearch Groovy client 150 | prior to the release of Grails 3.0. If you are using Grails 3.0 or later, then you should use the 151 | `invokedynamic` version of Groovy and the Gradle dependency above. 152 | 153 | Compiling Groovy Client 154 | ----------------------- 155 | 156 | To compile this code on your own, then run: 157 | 158 | ```bash 159 | $ gradle clean installDist 160 | ``` 161 | 162 | This will skip all tests and place the compiled jar in 163 | `./build/install/elasticsearch-groovy/elasticsearch-groovy-{version}.jar`. It will package all dependencies (e.g., 164 | `elasticsearch-{version}.jar`) into `./build/install/elasticsearch-groovy/lib`. 165 | 166 | Testing Groovy Client 167 | --------------------- 168 | 169 | The Groovy client makes use of the [Randomized Testing framework used by Elasticsearch 170 | itself](http://www.elasticsearch.org/blog/elasticsearch-testing-qa-increasing-coverage-randomizing-test-runs/). The unit 171 | tests and integration tests that this uses can be invoked with the same command: 172 | 173 | ```bash 174 | $ gradle clean test 175 | ``` 176 | 177 | The various `tests.*` and `es.*` system properties that are used by Elasticsearch are also used by the Gradle build 178 | script. As a result, any recommendation that suggests running `mvn clean test -DsystemProp=xyz` can be replaced with 179 | `gradle clean test -DsystemProp=xyz` (the only change was from `mvn` to `gradle`). This _only_ applies to the Groovy 180 | client. 181 | 182 | ### Testing with IntelliJ 183 | 184 | By default, IntelliJ will place all of the `compile`-time dependencies above the `testCompile` dependencies. In the case 185 | of the test frameworks used, this presents issues that _occasionally_ trigger test failures (that tell you to fix your 186 | classpath with respect to "test-framework.jar"). To fix this behavior, put your test dependencies above any non-test 187 | dependencies within IntelliJ. 188 | 189 | 1. Open `Project Structure` 190 | 2. Select `Modules` 191 | 192 | Suggested Groovy Settings 193 | ------------------------- 194 | 195 | Since the release of Java 7 (aka Java 1.7), higher level languages like Groovy have had access to the [`invokedynamic` 196 | JVM instruction]((http://groovy.codehaus.org/InvokeDynamic+support)). This avoids the need for some runtime code 197 | generation (e.g., `$callSiteArray`s) and it theoretically speeds up all Groovy code. In the Groovy world, there is still 198 | support for Java 5 and Java 6, which means that `invokedynamic` cannot be enabled by default. 199 | 200 | ### Compiling Groovy with `invokedynamic` support 201 | 202 | To support `invokedynamic` in your own Groovy project(s), at a minimum, you *must* include the `invokedynamic`-compiled 203 | Groovy jar, which the Groovy developers call the `indy` (`in`voke`dy`namic) jar. 204 | 205 | #### Gradle 206 | 207 | ```gradle 208 | repositories { 209 | mavenCentral() 210 | } 211 | 212 | dependencies { 213 | compile 'org.codehaus.groovy:groovy-all:2.4.5:indy' 214 | } 215 | ``` 216 | 217 | #### Maven 218 | 219 | ```xml 220 | 221 | 222 | org.codehaus.groovy 223 | groovy-all 224 | 2.4.5 225 | indy 226 | compile 227 | 228 | 229 | ``` 230 | 231 | ### Using `invokedynamic` in _your_ Groovy code 232 | 233 | After including the `indy` jar, you now _only_ have an `invokedynamic`-compatible Groovy runtime. All internal Groovy 234 | calls will use `invokedynamic`, as will any other Groovy code compiled with `invokedynamic` support (e.g., the 235 | Groovy client), but _your_ code must also be compiled with `invokedynamic` support to gain the benefits within your 236 | compiled jar(s). 237 | 238 | #### Gradle 239 | 240 | ```gradle 241 | apply plugin: 'groovy' 242 | 243 | // ... 244 | 245 | /** 246 | * Customize Groovy compilation. 247 | */ 248 | tasks.withType(GroovyCompile) { 249 | groovyOptions.optimizationOptions.indy = true 250 | } 251 | ``` 252 | 253 | #### Maven 254 | 255 | Maven has numerous ways to do this, and it largely depends on how you compile your Groovy code. If you are wrapping the 256 | Ant task, then add `indy="true"` to the Groovy compilation. Otherwise check your plugin's documentation. 257 | 258 | #### IntelliJ 259 | 260 | When allowing IntelliJ to control the compilation of your project, then you must enable `Invoke dynamic support` within 261 | the preferences for the `Groovy Compiler`. 262 | 263 | To change this setting: 264 | 265 | 1. Open `Preferences` 266 | 2. Select `Compiler` 267 | 3. Select `Groovy Compiler` 268 | 4. Check `Invoke dynamic support` 269 | 270 | With IntelliJ 13, I have noticed that it is sometimes necessary to manually rebuild the project because it loses track of the resource files. This happens infrequently, but it will cause practically every test to fail when it does happen. 271 | 272 | License 273 | ------- 274 | 275 | This software is licensed under the Apache 2 license, quoted below. 276 | 277 | Copyright 2009-2015 Elastic 278 | 279 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 280 | use this file except in compliance with the License. You may obtain a copy of 281 | the License at 282 | 283 | http://www.apache.org/licenses/LICENSE-2.0 284 | 285 | Unless required by applicable law or agreed to in writing, software 286 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 287 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 288 | License for the specific language governing permissions and limitations under 289 | the License. 290 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /////////////////////////////////////////////////// 21 | // // 22 | // Gradle Plugins // 23 | // // 24 | /////////////////////////////////////////////////// 25 | 26 | apply plugin: 'groovy' 27 | apply plugin: 'java-library-distribution' 28 | apply plugin: 'elasticsearch.build' 29 | apply plugin: 'nebula.optional-base' 30 | apply plugin: 'nebula.maven-base-publish' 31 | apply plugin: 'nebula.maven-scm' 32 | 33 | /////////////////////////////////////////////////// 34 | // // 35 | // Dependency Details // 36 | // // 37 | /////////////////////////////////////////////////// 38 | 39 | /** 40 | * Associate external dependencies with this project. Gradle only requires this step, and it does not require the 41 | * {@code versions} and {@code externalDeps} {@code Map}s. I find that they make it much easier to eyeball what is 42 | * happening as well as to find mistakes. 43 | *

44 | * Where order does not matter for the dependencies, then they are sorted alphabetically. 45 | */ 46 | dependencies { 47 | testCompile("org.elasticsearch.test:framework:${version}") { 48 | // tests use the locally compiled version of core; don't try to reach out to Maven to find it 49 | exclude group: 'org.elasticsearch', module: 'elasticsearch' 50 | } 51 | 52 | compile "org.elasticsearch:elasticsearch:${version}", 53 | "org.codehaus.groovy:groovy-all:2.4.5:indy" 54 | } 55 | 56 | /////////////////////////////////////////////////// 57 | // // 58 | // Project Details // 59 | // // 60 | /////////////////////////////////////////////////// 61 | 62 | // The minimum required Java version (this matches the Elasticsearch minimum Java version) 63 | sourceCompatibility = 1.8 64 | targetCompatibility = 1.8 65 | 66 | // Project details 67 | description = 'Official Groovy client for Elasticsearch' 68 | 69 | /////////////////////////////////////////////////// 70 | // // 71 | // Build Customization // 72 | // // 73 | /////////////////////////////////////////////////// 74 | 75 | /** 76 | * Configure Groovy compilation. 77 | */ 78 | tasks.withType(GroovyCompile) { 79 | // Enable the usage of invokedynamic instructions in compiled Groovy code 80 | // NOTE: This requires the "indy" version of the Groovy jar to take effect 81 | groovyOptions.optimizationOptions.indy = true 82 | } 83 | 84 | /////////////////////////////////////////////////// 85 | // // 86 | // Artifact Publishing // 87 | // // 88 | /////////////////////////////////////////////////// 89 | 90 | File secretsConfiguration = file('secrets.gradle') 91 | 92 | // If there is a secrets configuration file, then use it to overwrite properties 93 | if (secretsConfiguration.exists()) { 94 | // Meant to overwrite usernames/passwords and signing values 95 | apply from: secretsConfiguration 96 | } 97 | 98 | /** 99 | * Generate the Groovydoc jar on demand. 100 | */ 101 | task groovydocJar(type: Jar, dependsOn: groovydoc) { 102 | classifier = 'groovydoc' 103 | 104 | from groovydoc.destinationDir 105 | } 106 | 107 | /** 108 | * Generate the test jar on demand. 109 | *

110 | * The test jar is useful for creating integration tests written in Groovy. It will 111 | * appropriately ignore Groovy-created memory. All tests must extend either 112 | * {@code AbstractElasticsearchIntegrationTest} or {@code AbstractElasticsearchTestCase}. 113 | */ 114 | task testJar(type: Jar) { 115 | classifier = 'tests' 116 | 117 | from sourceSets.test.output 118 | 119 | // Any abstract tests are used as the basis to build from 120 | include '**/Abstract*' 121 | // The Abstract tests need the GroovyTestSanitizer in order to avoid failing due to 122 | // Groovy-created static memory 123 | include '**/GroovyTestSanitizer*' 124 | 125 | // Once it does the include, it leaves the empty directories, which are unwanted 126 | includeEmptyDirs = false 127 | } 128 | 129 | /** 130 | * Extra artifacts to publish along side the client jar. 131 | */ 132 | artifacts { 133 | archives groovydocJar 134 | archives sourcesJar 135 | archives testJar 136 | } 137 | 138 | publishing { 139 | publications { 140 | nebula { 141 | artifactId 'elasticsearch-groovy' 142 | } 143 | } 144 | } 145 | 146 | archivesBaseName = 'elasticsearch-groovy' 147 | 148 | /** 149 | * Sets up the publishing of the jars to a Maven repository. 150 | *

151 | * Archives and the POM file must be signed for full releases. They are less strict with snapshot releases, but they 152 | * should also be signed. 153 | */ 154 | 155 | /* 156 | uploadArchives { 157 | repositories { 158 | mavenDeployer { 159 | // sign the pom file 160 | beforeDeployment { 161 | signing.signPom(it) 162 | } 163 | 164 | // used to publish full releases 165 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 166 | authentication(userName: project.property('sonatypeUsername'), password: project.property('sonatypePassword')) 167 | } 168 | 169 | // used to publish snapshot dependencies 170 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 171 | authentication(userName: project.property('sonatypeUsername'), password: project.property('sonatypePassword')) 172 | } 173 | 174 | // property names containing a period (easily searchable within the POM via publishing scripts) 175 | def pomProperties = 176 | [ 'elasticsearch.version' : versions.elasticsearch, 177 | 'groovy.version' : versions.groovy, 178 | 'java.version.min' : versions.java, 179 | 'lucene.version' : versions.lucene ] 180 | 181 | // Add other fields and values to the POM file 182 | pom.project { 183 | // root-level project details 184 | name 'Elasticsearch Groovy Client' 185 | packaging 'jar' 186 | description project.description 187 | url 'https://github.com/elastic/elasticsearch-groovy' 188 | inceptionYear '2014' 189 | 190 | // Source Control details (GitHub) 191 | scm { 192 | connection 'scm:git:git@github.com:elastic/elasticsearch-groovy.git' 193 | developerConnection 'scm:git:git@github.com:elastic/elasticsearch-groovy.git' 194 | url 'https://github.com/elastic/elasticsearch-groovy' 195 | } 196 | 197 | // Using Apache 2 198 | licenses { 199 | license { 200 | name 'The Apache Software License, Version 2.0' 201 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 202 | distribution 'repo' 203 | } 204 | } 205 | 206 | // Arbitrary key/values 207 | properties pomProperties 208 | } 209 | } 210 | } 211 | } 212 | */ 213 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | File extrasDir = new File(settingsDir, '../..').getCanonicalFile() 21 | 22 | if (extrasDir.name.endsWith('-extra') == false) { 23 | throw new GradleException("elasticsearch-groovy must be checked out under an elasticsearch-extra directory, but found ${extrasDir.name}") 24 | } 25 | 26 | // elasticsearch does not require that its directory be named 'elasticsearch', but the -extra dir shares the same prefix! 27 | File elasticsearchDir = new File(extrasDir.parentFile, extrasDir.name[0..-7]) 28 | 29 | if (elasticsearchDir.exists() == false) { 30 | throw new GradleException("${elasticsearchDir.name} is missing as a sibling to ${extrasDir.name}") 31 | } 32 | 33 | // set the project root 34 | project(':').projectDir = new File(elasticsearchDir, 'buildSrc') -------------------------------------------------------------------------------- /docs/configuring-actionrequests.asciidoc: -------------------------------------------------------------------------------- 1 | == Understanding Requests and Responses Enhancements 2 | 3 | The Groovy client mostly works by adding http://groovy.codehaus.org/Creating+an+extension+module[Groovy extension modules] 4 | that add new methods to existing classes in the Java client. 5 | 6 | Two of the places that benefit the most from understanding the nature of these extensions are how the Groovy client provide 7 | closure support to configure _every_ `ActionRequest` and the resulting `ListenableActionFuture` that gets returned. 8 | 9 | `ActionRequest` is the base class used to represent a request being sent to your Elasticsearch cluster by the Java client. 10 | `ActionResponse` is the corresponding response type associated with the request. The Groovy client does _not_ provide 11 | enhancements to these base types, rather it generically enables configuration of `ActionRequest` by enhancing the `Client` 12 | types. 13 | 14 | :toc: 15 | 16 | === Configuring `ActionRequest` 17 | 18 | `ActionRequest` is just the generic type and by itself provides no concept of the action that to be performed. 19 | Because the many implementations within Elasticsearch contain very different parameters -- ranging from `source` content to 20 | simple parameters like `index`, `type`, and `id` -- the Groovy client cannot generically extend it to add desired 21 | functionality. The key is that none of the parameters are guaranteed to exist on a different `ActionRequest`. 22 | 23 | The easiest way to understand this is to compare two existing implementations of `ActionRequest`: 24 | 25 | 1. `org.elasticsearch.action.index.IndexRequest` 26 | + 27 | The `IndexRequest` is an `ActionRequest` that accepts _many_ different parameters, but the most interesting is the `source`, 28 | which is the source document that is added to your Elasticsearch `index`/`type` with an optional `id` (indexing without 29 | setting the `id` will cause Elasticsearch to generate a random ID for the document). 30 | 31 | 2. `org.elasticsearch.action.get.GetRequest` 32 | + 33 | The `GetRequest` is an `ActionRequest` that accepts a few different parameters, but the core parameters are the `index`, 34 | `type`, and `id` of the document to retrieve. 35 | 36 | In this case, both requests share _some_ parameters (specifically `index`, `type`, and `id`), but even in this simple case, 37 | their required-ness varies. 38 | 39 | As a result, the Groovy client does its best to add Groovy-friendly extensions on a per-request basis and changes are 40 | explicitly added to a given implementation. Simple requests that do not require (or allow) complicated setup are not enhanced 41 | in anyway, except that they can be configured with a `Closure` at the top level. This works using Groovy's own 42 | http://groovy.codehaus.org/groovy-jdk/java/lang/Object.html#with(groovy.lang.Closure)[with extension] to allow a given 43 | `Closure` to delegate _all_ calls to the associated object. For example: 44 | 45 | [source,groovy] 46 | --------------------------- 47 | // Java approach 48 | GetRequest javaGetRequest = new GetRequest(); 49 | 50 | request.index("my-index"); 51 | request.type("my-type"); 52 | request.id("my-id"); 53 | 54 | // Groovy enhanced approach 55 | GetRequest groovyGetRequest = new GetRequest().with { 56 | index "my-index" 57 | type "my-type" 58 | id "my-id" 59 | } 60 | --------------------------- 61 | 62 | Both approaches are completely valid and they yield an identically configured `GetRequest`. Using the `with` method delegates 63 | all method calls to the referenced `this` object (the newly constructed `GetRequest` in this example), which provides a 64 | syntacic shorthand for the Java approach. 65 | 66 | The benefit of using `with` is that it allows the API to be passed a `Closure` that completely configures the specific 67 | `ActionRequest`, which is exactly how the Groovy client works. Having been shown the above example, the Groovy-extended 68 | `Client` can be used to do the same thing: 69 | 70 | [source,groovy] 71 | --------------------------- 72 | // get a client 73 | Client client = /* ... */ 74 | 75 | // Groovy enhanced GetRequest 76 | GetResponse response = client.get { 77 | index "my-index" 78 | type "my-type" 79 | id "my-id" 80 | }.actionGet() 81 | --------------------------- 82 | 83 | The Groovy `Client` is extended to add `Closure` support to every request with the exception of any deprecated 84 | requests (e.g., facets). In the above example, we use the overloaded `get` method added via Groovy extensions. 85 | 86 | You can see exactly how all of the `Client` extensions are added by looking at the 87 | https://github.com/elasticsearch/elasticsearch-groovy/blob/master/src/main/groovy/org/elasticsearch/groovy/client/ClientExtensions.groovy[`ClientExtensions` class]. Each method is an added overload that invokes one of the two `doRequest` methods 88 | in the 89 | https://github.com/elasticsearch/elasticsearch-groovy/blob/master/src/main/groovy/org/elasticsearch/groovy/client/AbstractClientExtensions.groovy[`AbstractClientExtensions` class] to do the actual work and make the asynchronous request. 90 | 91 | The `Client` will generate the appropriate `ActionRequest` object for the given request and then apply the `Closure` using 92 | the `with` method that was demonstrated earlier. 93 | 94 | ==== A More Complicated `ActionRequest` 95 | 96 | The above example walked through one of the simplest `ActionRequest` implementations in order to show how it works. The next 97 | step is understanding how a more complicated `ActionRequest` is enhanced to work in a Groovy-friendly way. 98 | 99 | [source,groovy] 100 | --------------------------- 101 | // get a client 102 | Client client = /* ... */ 103 | 104 | // Groovy enhanced IndexRequest 105 | IndexResponse response = client.index { 106 | index "my-index" 107 | type "my-type" 108 | id "my-id" // Note: the ID optional as always 109 | source { 110 | user = "kimchy" 111 | message = "this is a tweet!" 112 | details { 113 | timestamp = new Date() 114 | } 115 | } 116 | }.actionGet() 117 | --------------------------- 118 | 119 | With the earlier `GetRequest` example in mind, you should recognize the `index`, `type`, and `id` methods being invoked. 120 | However, the `source` method is new here and it is given an inner `Closure`. 121 | 122 | First and foremost, it should be noted that _any_ method accepting a `Closure` (or any other Groovy-specific feature) is 123 | added by the Groovy client. Considering that point, it should be clear that the Groovy client has added at least one 124 | extension method to the `IndexRequest` class (you can see those added in the 125 | https://github.com/elasticsearch/elasticsearch-groovy/blob/master/src/main/groovy/org/elasticsearch/groovy/action/index/IndexRequestExtensions.groovy[IndexRequestExtensions class]). 126 | 127 | There, like other `ActionRequest` implementations that accept a `source`, the Groovy client adds an overloaded `source` 128 | method that accepts a `Closure`. Unlike the above examples, _that_ `Closure` is not interpretted using Groovy's `with` 129 | method. Instead, it uses extension methods provided by the Groovy client to enhance the `Closure` class itself. The idea 130 | behind those extensions is to treat the `Closure` as data rather than logic and the extensions are described in further 131 | detail in <>. 132 | 133 | === Configuring `ActionResponse` 134 | 135 | The other side of an `ActionRequest` is naturally an `ActionResponse`. Every `ActionRequest` has an `ActionResponse`; 136 | some share the same `ActionResponse` implementation of a related `ActionRequest`, but the key is that none are `void` 137 | (e.g., `search` and `searchScroll` are different requests with the same response: `SearchResponse`). 138 | 139 | All `Client` extensions use the `Client`'s thread pool to perform the passed `ActionRequest` asynchronously by default. The 140 | returned object is a `ListenableActionFuture` that results in an `ActionResponse` (or exception upon errors). For example, 141 | the method signature for the provided search method effectively is: 142 | 143 | [source,groovy] 144 | --------------------------- 145 | ListenableActionFuture search(Closure requestClosure) 146 | --------------------------- 147 | 148 | The above signature is applied to the `Client` so that asynchronous requests can be made like: 149 | 150 | [source,groovy] 151 | --------------------------- 152 | ListenableActionFuture responseFuture = client.search { 153 | indices 'index1', 'index2' 154 | types 'type1', 'type2' 155 | source { 156 | query { 157 | match { 158 | user = userId 159 | } 160 | } 161 | } 162 | } 163 | --------------------------- 164 | 165 | ==== Synchronous Responses 166 | 167 | As noted above, the `Closure`-based extension methods all respond asynchronously by returning a `ListenableActionFuture`. 168 | In your own code, you can immediately block until the actual `ActionResponse` is returned, thereby effectively converting 169 | the asynchronous request into a synchronous one. 170 | 171 | [source,groovy] 172 | --------------------------- 173 | import org.elasticsearch.ElasticsearchException 174 | 175 | try { 176 | ListenableActionFuture responseFuture = client.search { 177 | // ... 178 | } 179 | 180 | SearchResponse response = responseFuture.actionGet() 181 | } 182 | catch (ElasticsearchException e) { 183 | // request failed 184 | } 185 | --------------------------- 186 | 187 | [IMPORTANT] 188 | ==== 189 | It is good practice to monitor for exceptions and to log them for analysis. All requests can cause exceptions, including 190 | validation exceptions that are thrown when the `ActionRequest` is not fully or properly filled out, as well as exceptions 191 | that occur on the server (e.g., attempting to execute with an unknown script). 192 | ==== 193 | 194 | The above example will block the caller thread until the `SearchResponse` is returned or an exception is thrown. Overloaded 195 | versions of the `actionGet` method exist to allow you to put timeouts into your call. 196 | 197 | [WARNING] 198 | ==== 199 | Timeouts given to client-side requests are _only_ handled by the client. Therefore, if your request times out locally in, 200 | say, 5 seconds, then it only means that the client has given up. If that request were to trigger a script that loops 201 | infinitely on the server, then it will still be running on the server after your request times out. 202 | ==== 203 | 204 | ==== Asynchronous Response 205 | 206 | Asynchronous responses are only helpful if you can get the result of them. Fortunately, the `ListenableActionFuture` already 207 | allows you to listen using an `ActionFuture`, but the Groovy client expands on this to allow you to listen using a 208 | `Closure`. Additionally, it allows you to listen to success-or-failure, only-success, and only-failure. 209 | 210 | ===== Success or Failure 211 | 212 | [source,groovy] 213 | --------------------------- 214 | import org.elasticsearch.ElasticsearchException 215 | 216 | ListenableActionFuture responseFuture = client.search { 217 | // ... 218 | }.listener { response, exception -> 219 | if (exception != null) { 220 | // request failed due to the exception 221 | } 222 | else { 223 | // request was "successful" (true success 224 | // depends on the actual response) 225 | } 226 | } 227 | --------------------------- 228 | 229 | ===== Success or Failure, but Not Both 230 | 231 | Often times it is a lot more convenient to separate the logic used to handle success versus failure. With the Groovy client, 232 | this you can do just that by explicitly handling only success _or_ failure. 233 | 234 | [source,groovy] 235 | --------------------------- 236 | import org.elasticsearch.ElasticsearchException 237 | 238 | ListenableActionFuture responseFuture = client.search { 239 | // ... 240 | }.success { 241 | // the implicit "it" parameter is SearchResponse 242 | doSomethingWithSuccess(it.hits) 243 | }.failure { 244 | // the implicit "it" parameter is SearchResponse 245 | doSomethingWithFailure(it.hits) 246 | } 247 | --------------------------- 248 | 249 | [NOTE] 250 | ==== 251 | If you have a standard error handler that takes a single parameter -- a `Throwable` -- then you can use that as the 252 | failure listener using http://groovy.codehaus.org/Functional+Programming+with+Groovy[Groovy's functional programming 253 | features]. Specifically, you can pass a _method_ by reference using `.&` to have Groovy pass the method as a `Closure` 254 | (similar to a lambda expression). 255 | 256 | [source,groovy] 257 | --------------------------- 258 | import org.elasticsearch.ElasticsearchException 259 | 260 | ListenableActionFuture responseFuture = client.search { 261 | // ... 262 | }.success { 263 | // the implicit "it" parameter is SearchResponse 264 | doSomethingWithSuccess(it.hits) 265 | }.failure( 266 | // expected signature: 267 | // static [ignored] staticErrorHandler(Throwable t) { ... } 268 | SomeStaticClass.&staticErrorHandler 269 | ).failure( 270 | // expected signature: 271 | // [ignored] instanceErrorHandler(Throwable t) { ... } 272 | this.&instanceErrorHandler 273 | ).failure { 274 | SomeStaticClass.staticErrorHandler(it, "Custom message") 275 | } 276 | --------------------------- 277 | ==== 278 | 279 | It may be tempting to only ever listen to success -- don't. You want to know when exceptions happen as they provide a 280 | reason why something failed, which can help you to improve your application _or_ to submit bug reports to Elasticsearch. -------------------------------------------------------------------------------- /docs/enhanced-closures.asciidoc: -------------------------------------------------------------------------------- 1 | == Closure Enhancements Added by the Groovy Client 2 | 3 | One of the key sets of enhancements added by the Groovy client are the extensions added to Groovy's `Closure` type. Most of 4 | the Groovy client's other extensions depend heavily on the `Closure` extensions that are described here. 5 | 6 | :toc: 7 | 8 | === `Closure` to `Map` 9 | 10 | The key extension that is core to most others in the Groovy client is the ability to treat a `Closure` as data rather than 11 | logic (code). In order to get full use of the Groovy client, you only need to be _aware_ of these enhancements. 12 | 13 | [source,groovy] 14 | --------------- 15 | // Note: I made up the dates 16 | Closure closure = { 17 | user_id = 12345 18 | first_name = "Tony" 19 | last_name = "Stark" 20 | timestamp = new Date() 21 | employment { 22 | history = [{ 23 | name = "Stark Industries" 24 | start = "1980-01-13" 25 | end = "2007-05-03" 26 | fired = false 27 | }, { 28 | name = "S.H.I.E.L.D" 29 | start = "2011-10-15" 30 | }] 31 | } 32 | } 33 | --------------- 34 | 35 | [WARNING] 36 | ========= 37 | Convert any `Map` whose keys are _not_ `String` instances into a `Map` before using it within the `Closure` as a 38 | field's value. 39 | 40 | Any `Map` is used as a shallow copy. The idea is not to allow you to modify it, but that the use will be rare, that the 41 | time-to-live of the generated `Map` instance will be short, and to force the burden of translating any `Map` to 42 | `Map` onto you (the handling of conversion to `String` keys is certainly doable within a loop, but thing's like 43 | `Date` objects may give wildly different outputs from what _you_ would expect). 44 | ========= 45 | 46 | The above example of a semi-complicated `Closure` is one that could easily represent a document in your Elasticsearch index (if you 47 | have an index of inaccurate Marvel references). In general though, you can think of the above document if it were converted into JSON: 48 | 49 | [source,json] 50 | --------------- 51 | var json = { 52 | "user_id" : 12345, 53 | "first_name" : "Tony", 54 | "last_name" : "Stark", 55 | "timestamp" : new Date(), 56 | "employment" : { 57 | history : [{ 58 | "name" : "Stark Industries", 59 | "start" : "1980-01-13", 60 | "end" : "2007-05-03", 61 | "fired" : false 62 | }, { 63 | "name" : "S.H.I.E.L.D", 64 | "start" : "2011-10-15" 65 | }] 66 | } 67 | } 68 | --------------- 69 | 70 | ==== Converting the `Closure` 71 | 72 | Without understanding how it works, you can convert any `Closure` into a `Map` (which may include nested 73 | `Map`) using the added `Closure.asMap()` extension method. 74 | 75 | [source,groovy] 76 | --------------- 77 | Map mappedClosure = { /* ... */ }.asMap() // <- Extension Method Here 78 | --------------- 79 | 80 | And that's about it. The `asMap` extension method takes no arguments, and it uses 81 | http://groovy.codehaus.org/Replace+Inheritance+with+Delegation[Groovy's delegating features] in order to take over how the 82 | `Closure` is handled. 83 | 84 | [IMPORTANT] 85 | =========== 86 | Extension methods that the Groovy client provides that accept a `Closure` will do this for you. 87 | =========== 88 | 89 | ===== `Closure` Delegation 90 | 91 | This section can be skipped unless you are interested in what happens 92 | https://github.com/elasticsearch/elasticsearch-groovy/blob/master/src/main/groovy/org/elasticsearch/groovy/ClosureToMapConverter.groovy[under 93 | the hood in `ClosureToMapConverter`]. 94 | 95 | http://groovy.codehaus.org/Replace+Inheritance+with+Delegation[Groovy provides a feature known as delegation], which loosely 96 | allows calling code to take over on behalf of another object (forcibly delegate on the instance's behalf). There are a bunch 97 | of semantics that go into delegating an object, but the Groovy client _only_ uses the 98 | http://groovy.codehaus.org/api/groovy/lang/Closure.html#DELEGATE_FIRST[`DELEGATE_FIRST`] 99 | strategy to completely hijack the processing of any selected Groovy `Closure`; this means that all requests that come from 100 | the `Closure` are handled by the delegate -- the Groovy client's converter object. 101 | 102 | Delegation takes three different forms that allow's other Groovy code to completely take control of the delegated object: 103 | 104 | 1. Any "property" assignment is handled by `void setProperty(String propertyName, Object newValue)` 105 | + 106 | [source,groovy] 107 | --------------- 108 | { 109 | first_name = "First" 110 | middle_names = [ "One", "Two" ] 111 | age = 42 112 | nested = { 113 | /* ... */ 114 | } 115 | } 116 | --------------- 117 | + 118 | The above example would trigger four separate calls to `setProperty`: 119 | + 120 | .. `"first_name"` with `"First"` 121 | .. `"middle_names"` with `[ "One" , "Two" ]` 122 | .. `"age"` with `42` (`Integer`) 123 | .. `"nested"` with `{ /* ... */ }` (`Closure`) 124 | ... This will trigger a recursive call that results in a nested `Map` representing the passed-in `Closure`. 125 | 2. Any "method" call is handled by `void invokeMethod(String methodName, Object args)` 126 | + 127 | [source,groovy] 128 | --------------- 129 | { 130 | first_name "First" 131 | middle_names "One", "Two" 132 | age 42 133 | nested { 134 | /* ... */ 135 | } 136 | } 137 | --------------- 138 | + 139 | The above example would trigger four separate calls to `invokeMethod`: 140 | + 141 | [NOTE] 142 | ====== 143 | It is interesting to note that the incoming value is sent as a single-valued array, even though it is unfortunately not 144 | obviously passed as an `Object[]`. 145 | ====== 146 | + 147 | .. `"first_name"` with `[ "First" ]` 148 | .. `"middle_names"` with `[ "One" , "Two" ]` 149 | .. `"age"` with `[ 42 ]` (contains an `Integer`) 150 | .. `"nested"` with `[ { /* ... */ } ]` (contains a `Closure`) 151 | ... This will trigger a recursive call that results in a nested `Map` representing the passed-in `Closure`. 152 | 3. Any property that is read would use the `Object getProperty(String propertyName)`. 153 | + 154 | [source,groovy] 155 | --------------- 156 | { 157 | first_name = "First" 158 | last_name = "Last" 159 | name = first_name + ' ' + last_name 160 | } 161 | --------------- 162 | + 163 | This is a less commonly used method in the Groovy client, but it does come up from time to time. The Groovy client uses the 164 | `getProperty` method to return a value for `invokeMethod`. 165 | 166 | [NOTE] 167 | ====== 168 | To the Groovy client, the only difference between using the `setProperty` and `invokeMethod` approaches is how they _look_. Values that 169 | are passed through the `invokeMethod` approach are treated identically to those passed in via the `setProperty`. 170 | 171 | The two formats _can_ be mixed, but it is a good idea to pick a style and stick with it. For examples in the documentation, only nested 172 | objects (an inner `Closure`) use the non-`setProperty` approach. There is no reason for this except for consistency. Technically 173 | speaking, the `invokeMethod` does more work, but not enough to be significant. 174 | ====== 175 | 176 | ====== Even Deeper 177 | 178 | If you are still curious, the 179 | https://github.com/elasticsearch/elasticsearch-groovy/blob/master/src/main/groovy/org/elasticsearch/groovy/ClosureToMapConverter.groovy[`ClosureToMapConverter#convertValue`] 180 | can be evaluated to see what is happening. 181 | 182 | However, in the interest of completeness, there are a handful of special cases handled for conversion whenever 183 | values of the given type are come across: 184 | 185 | 1. `Closure` 186 | .. This is evaluated as a nested `Map` that is returned to replace the given value. 187 | 2. `Collection` 188 | .. Any `Collection` is evaluated by walking the `Collection` and returning a value per-item that is evaluated using this list 189 | recursively. The `Collection` itself is _not_ modified even if it is allowed to be modified. 190 | 3. `Object[]` or any primitive array 191 | .. Any array is converted into a `List`, and then it follows the same behavior as any `Collection`. 192 | 193 | === `Closure` to other types 194 | 195 | In addition to converting a `Closure` to a `Map`, you can also convert them into other types with a single method call (that 196 | usually depends on being converted into a `Map` first). 197 | 198 | These other conversion methods tend to be the ones _actually_ used internally. 199 | 200 | [cols="2*", options="header"] 201 | |=== 202 | | `Closure` Extension Method | Result 203 | | `asJsonBytes()` | JSON `byte[]` from `buildBytes(JSON)`. 204 | | `asJsonString()` | JSON `String` (not pretty printed) from `buildString(JSON)`. Mostly meant for debug output. 205 | | `asMap()` | `Map` described above. 206 | | `build(XContentType)` | `XContentBuilder` containing the `Closure` as a `Map` in the requested type. 207 | | `buildBytes(XContentType)` | `byte[]` using `build(XContentType)` to get the result of `XContentBuilder.bytes().toBytes()`. 208 | | `buildString(XContentType)` | `String` using `build(XContentType)` to get `XContentBuilder.string()`. 209 | -------------------------------------------------------------------------------- /docs/overview.asciidoc: -------------------------------------------------------------------------------- 1 | == Overview 2 | 3 | This is the official Groovy client for Elasticsearch. It is designed to be 100% compatible with the matching release of 4 | Elasticsearch's official Java client, only adding features that make it more convenient to use in Groovy without losing 5 | _any_ of the core functionality. 6 | 7 | The Groovy client is able to maintain compatibility by using a Groovy 8 | http://groovy.codehaus.org/Creating+an+extension+module[feature known as Extension modules]. We make heavy use of this 9 | approach to extend the Java client _in place_ to maintain compatibility and simplicity. 10 | 11 | By taking this approach, every Elasticsearch Java client example works with the Groovy client and they can be extended 12 | easily to simplify development. In general, the Groovy client has a few core goals: 13 | 14 | 1. Provide 100% compatibility with the Java client and therefore feature parity. 15 | 2. Provide Groovy-friendly syntax where it improves the API. 16 | 3. Avoid adding complexity for the sake of supporting Groovy. 17 | 4. Avoid unnecessarily compromising the performance of the Java client for the sake of Groovy. 18 | 19 | === Groovy and Java Requirements 20 | 21 | tl;dr Use Java 7u60 or later 22 | 23 | The Groovy client is a literal extension of the Java client and therefore it has all of the same requirements. One of 24 | the most pressing requirements is the version of Java that is required. 25 | 26 | Supported versions of the Elasticsearch Java client require Java 7 or later to be able to run with a recommendation to 27 | stay up-to-date with JVM releases, and to particularly avoid Java releases prior to Java 7 update 55. The Groovy client 28 | gets more specific due to a new Java instruction known as `invokedynamic`, or `indy` for short (__in__voke__dy__namic), 29 | which we take advantage of to get higher performance, lower memory usage, and even smaller jar files in some cases. 30 | 31 | With the `indy` requirement comes a http://groovy.codehaus.org/InvokeDynamic+support[further restriction on Java 7 32 | JVMs] from the Groovy maintainers. They recommend that you should never use Java versions 7u21 through 7u55 due to more 33 | than one https://bugs.openjdk.java.net/browse/JDK-8033669[JVM bug] (e.g., 34 | https://bugs.openjdk.java.net/browse/JDK-8034024[here's another]). 35 | 36 | As a result, they explicitly suggest and our Groovy client implicitly requires Java 7u60 or later. -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Licensed to Elasticsearch under one or more contributor 2 | # license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright 4 | # ownership. Elasticsearch licenses this file to you under 5 | # the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | ################################################# 19 | # # 20 | # Preset System Properties # 21 | # # 22 | ################################################# 23 | # 24 | # Any system property can be explicitly overridden without touching this file by adding the system property on the 25 | # command line. For example: 26 | # 27 | # $ gradle [tasks] -Djava.awt.headless=false 28 | # 29 | # Any environment variable can be overridden by simply having it defined in your environment. 30 | # 31 | 32 | # By default, assume that we want local test nodes. 33 | # 34 | # This can be overridden by supplying the corresponding environment variable _or_ -Des.node.local=false 35 | envES_TEST_LOCAL=true 36 | 37 | # It's unlikely that you will want to change this value and it is not intended to be overridden. 38 | systemProp.java.awt.headless=true 39 | 40 | 41 | ################################################# 42 | # # 43 | # Gradle Build Properties # 44 | # # 45 | ################################################# 46 | # 47 | # You can add a "secrets.gradle" file at this level and it will be loaded _after_ this file, thus allowing you 48 | # to override all of these properties. Example "secrets.gradle" file: 49 | # 50 | # // All properties to overwrite 51 | # def secrets = [ 52 | # // Signing Configuration for jar/pom signing 53 | # 'signing.keyId' : '24875D73', 54 | # 'signing.password' : 'secret gpg password', 55 | # 'signing.secretKeyRingFile' : '/Users/local-username/.gnupg/secring.gpg', 56 | # 57 | # // Sonatype access parameters 58 | # 'sonatypePassword' : 'sonatype password', 59 | # 'sonatypeUsername' : 'username' 60 | # ] 61 | # 62 | # // Set/overwrite secret properties 63 | # secrets.each { 64 | # project.setProperty(it.key, it.value) 65 | # } 66 | # 67 | # These are properties that are meant to be used during the build. Any of these properties can be overriden on the 68 | # command line. For example: 69 | # 70 | # $ gradle [tasks] -PsonatypeUsername=my-username 71 | # 72 | 73 | # The username and password used for publishing to Maven. 74 | # 75 | # $ gradle [tasks] -PsonatypeUsername=my-username "-PsonatypePassword=my password" 76 | # 77 | # These are placeholders and you should _never_ set these values here to avoid accidentally committing them to the Git 78 | # repository. 79 | sonatypeUsername=username 80 | sonatypePassword=password 81 | 82 | # The signing properties are required to sign the jars and generated POM file. 83 | # 84 | # To setup, you should install GPG (brew install gpg on a Mac). From there, you need to generate a new key. To 85 | # generate a new key, run: 86 | # 87 | # 1. gpg --gen-key (fill out as requested) 88 | # 2. gpg --list-keys 89 | # 90 | # From step 2, you should find a line like "pub 2048R/24875D73 2014-11-26" where "24875D73" is the keyId. The 91 | # password will be the value associated with what you typed in while generating the key. The secretKeyRingFile is by 92 | # default stored in your "~/.gnupg/secring.gpg" (unfortunately using that value does not work as '~' is apparently 93 | # escaped by Gradle). 94 | # 95 | # $ gradle [tasks] -Psigning.keyId=24875D73 "-Psigning.password=my secret" -Psigning.secretKeyRingFile=/path/to.gpg 96 | # 97 | # These are placeholders and you should _never_ set the key/password values here to avoid accidentally committing them 98 | # to the Git repository. 99 | signing.keyId=24875D73 100 | signing.password=secret 101 | signing.secretKeyRingFile=/Users/username/.gnupg/secring.gpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | File extrasDir = new File(settingsDir, '..').getCanonicalFile() 21 | 22 | if (extrasDir.name.endsWith('-extra') == false) { 23 | throw new GradleException("elasticsearch-groovy must be checked out under an elasticsearch-extra directory, but found ${extrasDir.name}") 24 | } 25 | 26 | File elasticsearchDir = new File(extrasDir.parentFile, extrasDir.name[0..-7]) 27 | 28 | // NOTE: we ensure the elasticsearch dir is present in buildSrc 29 | project(':').projectDir = elasticsearchDir 30 | 31 | apply from: "${elasticsearchDir}/settings.gradle" -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/ClosureExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import groovy.transform.CompileStatic 22 | import groovy.transform.TypeChecked 23 | 24 | /** 25 | * {@code ClosureExtensions} adds convenient behaviors to Groovy's {@link Closure} class, such as the ability to convert 26 | * a {@code Closure} into a {@link Map} with {@link String} keys and {@link Object} values. 27 | */ 28 | @CompileStatic 29 | @TypeChecked 30 | class ClosureExtensions { 31 | /** 32 | * Convert the self-referenced {@link Closure} into a {@link Map} with {@link String} keys as {@link Object} values. 33 | *

34 |      * Map<String, Object> closureMap = {
35 |      *   name {
36 |      *     first = "firstName"
37 |      *     last = "lastName"
38 |      *   }
39 |      *   nested {
40 |      *     object {
41 |      *       property = "value"
42 |      *     }
43 |      *   }
44 |      * }.asMap()
45 |      * 
46 | * 47 | * @param self The {@code this} reference for the {@link Closure} 48 | * @return Never {@code null}. Can be {@link Map#isEmpty() empty}. 49 | * @throws NullPointerException if {@code self} is {@code null} 50 | * @see ClosureToMapConverter#mapClosure(Closure) 51 | */ 52 | static Map asMap(Closure self) { 53 | ClosureToMapConverter.mapClosure(self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/ClosureToMapConverter.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import groovy.transform.CompileStatic 22 | import groovy.transform.TypeChecked 23 | 24 | /** 25 | * {@code ClosureToMapConverter} serves as a utility to convert {@link Closure}s into {@link Map}s with {@link String} 26 | * keys and {@link Object} values. The values can be {@link List}s, {@link Map}s, or other values (normal objects). In 27 | * general, this serves as a convenient way to load JSON-like syntax in Groovy and convert it into a format usable by 28 | * Elasticsearch. 29 | *
 30 |  * Map<String, Object> map = mapClosure {
 31 |  *   name {
 32 |  *     first = "Example"
 33 |  *     middle = ["First", "Second"]
 34 |  *     last = "Name"
 35 |  *   }
 36 |  *   details {
 37 |  *     nested {
 38 |  *       user_id = 1234
 39 |  *       timestamp = new Date()
 40 |  *     }
 41 |  *   }
 42 |  * }
 43 |  * 
44 | * This class is added to {@link Closure}s automatically via the {@link ClosureExtensions} class, so the above code can 45 | * be slightly simplified by avoiding the static import of {@link ClosureToMapConverter#mapClosure} and instead using 46 | * the {@link ClosureExtensions#asMap(Closure)} extension method. 47 | *
 48 |  * Map<String, Object> map = {
 49 |  *   name {
 50 |  *     first = "Example"
 51 |  *     middle = ["First", "Second"]
 52 |  *     last = "Name"
 53 |  *   }
 54 |  *   details {
 55 |  *     nested {
 56 |  *       user_id = 1234
 57 |  *       timestamp = new Date()
 58 |  *     }
 59 |  *   }
 60 |  * }.asMap()
 61 |  * 
62 | * It's important to note that the field name's can be specified as methods or properties. This means that 63 | *
 64 |  * {
 65 |  *   name {
 66 |  *     first "Example"
 67 |  *   }
 68 |  * }
 69 |  * 
70 | * is treated the same way as 71 | *
 72 |  * {
 73 |  *   name = {
 74 |  *     first = "Example"
 75 |  *   }
 76 |  * }
 77 |  * 
78 | * Mixing the two styles is allowed, but consistency is naturally very important for code readability. 79 | *

80 | * Instances should never be reused. Instances of this class are not thread safe, but separate instances do not share 81 | * any state and therefore multiple instances can run in parallel. 82 | * @see ClosureExtensions#asMap(Closure) 83 | */ 84 | @CompileStatic 85 | @TypeChecked 86 | class ClosureToMapConverter { 87 | /** 88 | * Convert the {@code closure} into a {@link Map}. 89 | * 90 | * @param closure The closure to convert. 91 | * @return Never {@code null}. Can be {@link Map#isEmpty() empty}. 92 | */ 93 | static Map mapClosure(Closure closure) { 94 | mapClosureWithOwner(closure, closure.owner) 95 | } 96 | 97 | /** 98 | * Convert the {@code closure} into a {@link Map}. 99 | *

100 | * The {@code owner} comes up when users specify variables within the {@link Closure}. 101 | * 102 | * @param closure The closure to convert. 103 | * @Param owner The owner of the {@code closure}. 104 | * @return Never {@code null}. Can be {@link Map#isEmpty() empty}. 105 | */ 106 | private static Map mapClosureWithOwner(Closure closure, Object owner) { 107 | new ClosureToMapConverter(closure, owner).convert() 108 | } 109 | 110 | /** 111 | * The "buildName" is used to maintain state for names like "field.innerField.nested", which 112 | * is actually 3 separate fields without recognizing it. 113 | *

114 |      * Map<String, Object> map = mapClosure {
115 |      *   field.innerField.nested = value
116 |      * }
117 |      * 
118 | * This allows us to parse the field name as literally "field.innerField.nested" by tracking requests 119 | * for the field "field", followed by "innerField" and finally attempting to set "nested". 120 | *

121 | * This format should only be used when for things like changing settings or searching (e.g., 122 | * a match request against an inner field or a nested query). 123 | */ 124 | private String buildName = null 125 | /** 126 | * The unraveled {@link Closure} after calling {@link #convert()}. 127 | */ 128 | private final Map map = [:] 129 | /** 130 | * The closure that is unraveled into the {@link #map}. 131 | */ 132 | final Closure closure 133 | /** 134 | * The owner of the root {@link #closure}. 135 | *

136 | * Inner {@link Closure}s handle their owner differently than the root one, so it's important to track the root's 137 | * owner. 138 | */ 139 | final Object rootOwner 140 | 141 | /** 142 | * Construct a new {@link ClosureToMapConverter} that delegates the {@code closure} to call the constructed 143 | * instance ({@code this}) when it is invoked. These calls are used to unravel the {@code closure} into the 144 | * {@link #map}. 145 | * 146 | * @param closure The {@link Closure} to convert into a {@link Map}. 147 | * @throws NullPointerException if {@code closure} is {@code null} 148 | */ 149 | private ClosureToMapConverter(Closure closure, Object owner) { 150 | // required 151 | this.closure = (Closure)closure.clone() 152 | this.rootOwner = owner 153 | 154 | // When looking up properties and invoking methods, it first looks at the delegate (THIS) for the value. If 155 | // the delegate (THIS) does not have it, then it will check the owner for the value (effectively the closure). 156 | this.closure.delegate = this 157 | // Note: Using OWNER_FIRST (the default) does not work except for non-nested closures. 158 | // This class will take it over entirely, as the delegate, for property access, but not method invocation. 159 | this.closure.resolveStrategy = Closure.DELEGATE_FIRST 160 | } 161 | 162 | /** 163 | * Trigger the conversion of the {@link #closure} to the {@link #map}. 164 | *

165 | * This method should only be invoked once. 166 | * 167 | * @return Never {@code null}. {@link Map#isEmpty() Empty} if the {@code closure} does not assign any properties. 168 | */ 169 | Map convert() { 170 | // invoke the closure, thus triggering calls to the delegate (this) 171 | closure.call() 172 | 173 | map 174 | } 175 | 176 | /** 177 | * Called when the {@link #closure} is delegating to {@code this} instance for method invocations. For example 178 | *

179 |      * { username "kimchy" }
180 |      * 
181 | * The above {@link Closure} would pass a {@code methodName} set to "username" and {@code args} as "kimchy" 182 | * within a single element {@code Object[]}. 183 | *
184 |      * {
185 |      *   user {
186 |      *     id 1234
187 |      *     name "kimchy"
188 |      *   }
189 |      * }
190 |      * 
191 | * This {@link Closure} would pass a {@code methodName} set to "user" and {@code args} as the user closure within 192 | * a single element {@code Object[]}. A nested invocation of that closure would pass a {@code methodName} of 193 | * "id" and {@code args} as 1234 within a single element {@code Object[]}. A separate nested invocation of that 194 | * closure would handle the "name" field. 195 | */ 196 | @Override 197 | Object invokeMethod(String methodName, Object args) { 198 | Object value = args 199 | 200 | // single element arrays are the most expected form: 201 | // Map map = { 202 | // name { 203 | // value = "xyz" 204 | // } 205 | // } 206 | // methodName would be "name" and args would be the closure 207 | if (args instanceof Object[] && ((Object[])args).length == 1) { 208 | value = ((Object[])args)[0] 209 | } 210 | 211 | // assign the value of the would-be method 212 | setProperty(methodName, value) 213 | 214 | // return the value 215 | getProperty(methodName) 216 | } 217 | 218 | /** 219 | * Get the value defined in the {@link #map} with the {@code propertyName}. 220 | *

221 | * This method has a side-effect if you are requesting a {@code propertyName} that is unrecognized to both 222 | * {@code this} (its internal {@link #map}) or the {@link #rootOwner}. In that case, the this method will remember 223 | * the {@code propertyName} for future use with {@link #setProperty} or {@link #invokeMethod}. 224 | *

225 | * The reason that it keeps track of this the {@code propertyName} is because this method is only called when a 226 | * value is sought. In general, that only occurs on the right hand side (e.g., x = y, where y is the right 227 | * hand side). However, if the value is unrecognized, then it's either an error or, more likely, it's being 228 | * used in the form of "x.y.z = a" rather than "x { y { z = a } }". Doing this should is considered an error. 229 | *

230 |      * Map<String, Object> map = mapClosure {
231 |      *   x.y.z = 123
232 |      *   a = x.y.z     // BAD!!
233 |      * }
234 |      * 
235 | * If you really need similar functionality, then define a temporary variable outside of the 236 | * {@code Closure} and use it. 237 | *
238 |      * int value = 123
239 |      *
240 |      * Map<String, Object> map = mapClosure {
241 |      *   x.y.z = value
242 |      *   a = value     // GOOD!!
243 |      * }
244 |      * 
245 | *

246 | * Perhaps unexpectedly, this will never return property values unless given the full name. When Groovy 247 | * is unraveling the shorthand version, this method is called twice in the above example. Once with "x", again with 248 | * "y", and finally the setter is called against "y" for "z". By tracking "x" and "y", we can appropriately create 249 | * the "x.y.z" key that is intended. Because we are building it, we don't want to return any value that we happen 250 | * across "along the way" because each key in this format is effectively not associated with others. 251 | */ 252 | @Override 253 | Object getProperty(String propertyName) { 254 | Object returned = this 255 | 256 | // if we know about it, then return the value 257 | if (map.containsKey(propertyName)) { 258 | returned = map[propertyName] 259 | } 260 | // NOTE: The following blocks are here to handle the less common "key1.key2" on the left-hand side 261 | // This is meant to be less common 262 | // if we don't know about it, then start building the actual key name 263 | // (key is in form of "key1.key2.key3" and this will be "key1" 264 | else if (buildName == null) { 265 | // If the OWNER of the closure has the value, then we want to use that value because it will get assigned. 266 | // NOTE: This "owner" is only the same as "closure.owner" for the root closure. 267 | if (rootOwner.hasProperty(propertyName)) { 268 | // the owner has it, so just return that value (this _should_ be on the right hand side used as a value) 269 | returned = ((GroovyObject)rootOwner).getProperty(propertyName) 270 | } 271 | else { 272 | buildName = propertyName 273 | } 274 | } 275 | // continuation from above where it's now "key2"; this method would never get "key3" with the inline example 276 | else { 277 | buildName += '.' + propertyName 278 | } 279 | 280 | returned 281 | } 282 | 283 | /** 284 | * Called when the {@link #closure} is delegating to {@code this} instance. For example 285 | *

286 |      * { username = "kimchy" }
287 |      * 
288 | * The above {@link Closure} would pass a {@code propertyName} set to "username" and a {@code newValue} as "kimchy". 289 | */ 290 | @Override 291 | void setProperty(String propertyName, Object newValue) { 292 | String fullName = propertyName 293 | 294 | // If we are maintaining state trying to load the name 295 | if (buildName != null) { 296 | fullName = buildName + "." + propertyName 297 | // We just now saw "key3" from the getProperty method, so we can throw away the name now 298 | buildName = null 299 | } 300 | 301 | map[fullName] = convertValue(newValue) 302 | } 303 | 304 | /** 305 | * Convert the passed in {@code value} into an object that is not a {@link Closure}, and convert any 306 | * {@link Collection}s into {@link List}s. 307 | *

308 | * This will recursively call itself as necessary. 309 | * 310 | * @param value The incoming parameter to convert if necessary ({@link Closure}s and {@link Collection}s) 311 | * @return {@code value} as-is unless it is a {@link Closure} or {@link Collection}. Otherwise the unraveled 312 | * value of those objects. 313 | * @throws IllegalArgumentException if you attempt to reuse a shorthand property on the right hand side (e.g., 314 | * "x.y.z = a; b = x.y.z;" where "x.y.z" is the shorthand property) 315 | */ 316 | private Object convertValue(Object value) { 317 | Object ret = value 318 | 319 | // avoid handling shorthand assignments (technically we could check for this, then build the name here, but 320 | // this is a very bad code smell; just use a temporary variable!) 321 | if (value instanceof ClosureToMapConverter) { 322 | throw new IllegalArgumentException( 323 | "value is a ClosureToMapConverter. This means that you are trying to reuse a shorthand " + 324 | "property! (For example, { x.y.z = 123; a = x.y.z }. 'x.y.z' cannot be referenced on the right " + 325 | "hand side! Use a temporary variable from outside the closure instead.)") 326 | } 327 | // enable nested objects 328 | else if (value instanceof Closure) { 329 | // avoid overwriting this instance's map; maintain the owner! 330 | ret = mapClosureWithOwner(value, rootOwner) 331 | } 332 | // unravel collections into a List 333 | else if (value instanceof Collection) { 334 | // use _this_ method by passing its method address to be invoked 335 | ret = ((Collection)value).collect(this.&convertValue) 336 | } 337 | // unravel arrays into a List (note: this hits native arrays like int[]) 338 | else if (value instanceof Object[] || (value != null && value.getClass().isArray())) { 339 | ret = (value as List).collect(this.&convertValue) 340 | } 341 | 342 | ret 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/ListenableActionFutureExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action 20 | 21 | import org.elasticsearch.action.ActionListener 22 | import org.elasticsearch.action.ListenableActionFuture 23 | 24 | /** 25 | * {@code ListenableActionFutureExtensions} adds Groovy-friendly extensions to {@link ListenableActionFuture}s to 26 | * enable sometimes-simpler listener support. 27 | *

28 | * Note: All of the listener methods behave like "add" methods. "add" was not used to avoid colliding with Java 29 | * variants to avoid unexpected issues ({@link Closure} is-a {@link Runnable}, which can cause confusion without explicit casting). 30 | *

 31 |  * listenableActionFuture.listener { response, e ->
 32 |  *     // ...
 33 |  * }.successListener {
 34 |  *     // ...
 35 |  * }.failureListener {
 36 |  *     // ...
 37 |  * }
 38 |  * 
39 | */ 40 | class ListenableActionFutureExtensions { 41 | /** 42 | * Adds the {@code listener} as a wrapped {@link ActionListener}. In both response (success or failure), the first 43 | * parameter passed to the {@code listener} is the {@link ListenableActionFuture} itself ({@code self}), the second 44 | * parameter is the successful response ({@code null} upon failure), and the third parameter is the 45 | * {@link Throwable} exception ({@code null} success). 46 | *
 47 |      * listenableActionFuture.listener { T response, Throwable t ->
 48 |      *     if (t != null) {
 49 |      *         // error
 50 |      *     }
 51 |      *     else {
 52 |      *         // success (NOTE: response could still be null if that was the value returned)
 53 |      *     }
 54 |      * }
 55 |      * 
56 | * The other listener extensions provide specific handling for either 57 | * 58 | * @param self The {@code this} reference for the {@link ListenableActionFuture}. 59 | * @param listener The generic listener 60 | * @return Always {@code self}. 61 | * @throws NullPointerException if any parameter is {@code null} 62 | */ 63 | static ListenableActionFuture listener(ListenableActionFuture self, Closure listener) { 64 | checkNotNull(listener, "listener cannot be null") 65 | 66 | self.addListener(new ActionListener() { 67 | @Override 68 | void onResponse(Object t) { 69 | listener.call(t, null) 70 | } 71 | 72 | @Override 73 | void onFailure(Exception e) { 74 | listener.call(null, e) 75 | } 76 | }) 77 | 78 | self 79 | } 80 | 81 | /** 82 | * Adds the {@code listener} as a wrapped {@link ActionListener} that only handles 83 | * {@link ActionListener#onResponse success}. 84 | *
 85 |      * listenableActionFuture.successListener { response ->
 86 |      *     // response can be null if it was a valid return value (same as actionGet())
 87 |      * }
 88 |      * 
89 | * It is very important to note that successful responses are inherently not guaranteed to triggered because not 90 | * every {@link ListenableActionFuture} will succeed. It is therefore a good idea to use this in conjunction with 91 | * {@link #failureListener(ListenableActionFuture, Closure)}. 92 | * 93 | * @param self The {@code this} reference for the {@link ListenableActionFuture}. 94 | * @param listener The success listener 95 | * @return Always {@code self}. 96 | * @throws NullPointerException if any parameter is {@code null} 97 | */ 98 | static ListenableActionFuture successListener(ListenableActionFuture self, Closure listener) { 99 | checkNotNull(listener, "listener cannot be null") 100 | 101 | self.addListener(new ActionListener() { 102 | @Override 103 | void onResponse(Object t) { 104 | listener.call(t) 105 | } 106 | 107 | @Override 108 | void onFailure(Exception e) { 109 | // success listener ignores failure 110 | } 111 | }) 112 | 113 | self 114 | } 115 | 116 | /** 117 | * Adds the {@code listener} as a wrapped {@link ActionListener} that only handles 118 | * {@link ActionListener#onFailure(Exception)} failure}. 119 | *
120 |      * listenableActionFuture.failureListener { e ->
121 |      *     // e is never null
122 |      * }
123 |      * 
124 | * It is very important to note that failure responses are inherently not guaranteed to be triggered because not 125 | * every {@link ListenableActionFuture} will fail. It is therefore a good idea to use this in conjunction with 126 | * {@link #successListener(ListenableActionFuture, Closure)}. 127 | * 128 | * @param self The {@code this} reference for the {@link ListenableActionFuture}. 129 | * @param listener The failure listener 130 | * @return Always {@code self}. 131 | * @throws NullPointerException if any parameter is {@code null} 132 | */ 133 | static ListenableActionFuture failureListener(ListenableActionFuture self, Closure listener) { 134 | checkNotNull(listener, "listener cannot be null") 135 | 136 | self.addListener(new ActionListener() { 137 | @Override 138 | void onResponse(Object t) { 139 | // failure listener ignores success 140 | } 141 | 142 | @Override 143 | void onFailure(Exception e) { 144 | listener.call(e) 145 | } 146 | }) 147 | 148 | self 149 | } 150 | 151 | /** 152 | * Verify that {@code isNotNull} is not {@code null}. 153 | *

154 | * If it is {@code null}, then this will throw a {@link NullPointerException} 155 | * with the specified {@code message}. 156 | * @param isNotNull The field to verify. 157 | * @param message The message to use if it is {@code null}. 158 | * @throws NullPointerException if {@code isNotNull} is {@code null} 159 | */ 160 | private void checkNotNull(Object isNotNull, String message) { 161 | if (isNotNull == null) { 162 | throw new NullPointerException(message) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/cluster/repositories/put/PutRepositoryRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.cluster.repositories.put 20 | 21 | import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest 22 | import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequestBuilder 23 | import org.elasticsearch.client.ClusterAdminClient 24 | 25 | /** 26 | * {@code PutRepositoryRequestExtensions} provides Groovy-friendly {@link PutRepositoryRequest} extensions. 27 | *

28 | * Note: This extension intentionally does _not_ provide a {@code Closure} overload of 29 | * {@link PutRepositoryRequest#source(java.util.Map)} because it is interpreted the same as using 30 | * {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#with} and a {@code Closure}. 31 | * @see ClusterAdminClient 32 | */ 33 | class PutRepositoryRequestExtensions { 34 | /** 35 | * Sets the {@code settings} for the cluster repository. 36 | *

37 |      * PutRepositoryResponse response = client.admin.cluster.putRepository {
38 |      *   name "repo-name"
39 |      *   type "fs"
40 |      *   settings {
41 |      *     location = "/mount/backups/my_backup"
42 |      *     compress = true
43 |      *   }
44 |      * }.actionGet()
45 |      * 
46 | * 47 | * @param self The {@code this} reference for the {@link PutRepositoryRequest}. 48 | * @param settings The repository settings 49 | * @return Always {@code self}. 50 | * @throws NullPointerException if any parameter is {@code null} 51 | */ 52 | static PutRepositoryRequest settings(PutRepositoryRequest self, Closure settings) { 53 | self.settings(settings.asMap()) 54 | } 55 | 56 | /** 57 | * Sets the {@code settings} for the cluster repository. 58 | * 59 | * @param self The {@code this} reference for the {@link PutRepositoryRequestBuilder}. 60 | * @param source The repository settings 61 | * @return Always {@code self}. 62 | * @throws NullPointerException if any parameter is {@code null} 63 | */ 64 | static PutRepositoryRequestBuilder setSettings(PutRepositoryRequestBuilder self, Closure settings) { 65 | self.setSettings(settings.asMap()) 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/cluster/settings/ClusterUpdateSettingsRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.cluster.settings 20 | 21 | import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest 22 | import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequestBuilder 23 | import org.elasticsearch.client.ClusterAdminClient 24 | 25 | /** 26 | * {@code ClusterUpdateSettingsRequestExtensions} provides Groovy-friendly {@link ClusterUpdateSettingsRequest} 27 | * extensions. 28 | * @see ClusterAdminClient 29 | */ 30 | class ClusterUpdateSettingsRequestExtensions { 31 | /** 32 | * Updates the persistent {@code settings} for the entire cluster. 33 | * 34 | * @param self The {@code this} reference for the {@link ClusterUpdateSettingsRequest}. 35 | * @param settings The persistent settings 36 | * @return Always {@code self}. 37 | * @throws NullPointerException if any parameter is {@code null} 38 | */ 39 | static ClusterUpdateSettingsRequest persistentSettings(ClusterUpdateSettingsRequest self, Closure settings) { 40 | self.persistentSettings(settings.asMap()) 41 | } 42 | 43 | /** 44 | * Updates the transient {@code settings} for the entire cluster. 45 | * 46 | * @param self The {@code this} reference for the {@link ClusterUpdateSettingsRequest}. 47 | * @param settings The transient settings 48 | * @return Always {@code self}. 49 | * @throws NullPointerException if any parameter is {@code null} 50 | */ 51 | static ClusterUpdateSettingsRequest transientSettings(ClusterUpdateSettingsRequest self, Closure settings) { 52 | self.transientSettings(settings.asMap()) 53 | } 54 | 55 | /** 56 | * Updates the persistent {@code settings} for the entire cluster. 57 | * 58 | * @param self The {@code this} reference for the {@link ClusterUpdateSettingsRequestBuilder}. 59 | * @param source The persistent settings 60 | * @return Always {@code self}. 61 | * @throws NullPointerException if any parameter is {@code null} 62 | */ 63 | static ClusterUpdateSettingsRequestBuilder setPersistentSettings(ClusterUpdateSettingsRequestBuilder self, 64 | Closure settings) { 65 | self.setPersistentSettings(settings.asMap()) 66 | } 67 | 68 | /** 69 | * Updates the transient {@code settings} for the entire cluster. 70 | * 71 | * @param self The {@code this} reference for the {@link ClusterUpdateSettingsRequestBuilder}. 72 | * @param source The transient settings 73 | * @return Always {@code self}. 74 | * @throws NullPointerException if any parameter is {@code null} 75 | */ 76 | static ClusterUpdateSettingsRequestBuilder setTransientSettings(ClusterUpdateSettingsRequestBuilder self, 77 | Closure settings) { 78 | self.setTransientSettings(settings.asMap()) 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/cluster/snapshots/create/CreateSnapshotRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.cluster.snapshots.create 20 | 21 | import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest 22 | import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder 23 | import org.elasticsearch.client.ClusterAdminClient 24 | 25 | /** 26 | * {@code CreateSnapshotRequestExtensions} provides Groovy-friendly {@link CreateSnapshotRequest} extensions. 27 | *

28 | * Note: This extension intentionally does _not_ provide a {@code Closure} overload of 29 | * {@link CreateSnapshotRequest#source(java.util.Map)} because it is interpreted the same as using 30 | * {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#with} and a {@code Closure}. 31 | * @see ClusterAdminClient 32 | */ 33 | class CreateSnapshotRequestExtensions { 34 | /** 35 | * Sets the {@code settings} for the snapshot. 36 | *

37 |      * CreateSnapshotResponse response = client.admin.cluster.createSnapshot {
38 |      *   repository "snapshot_repo"
39 |      *   snapshot "snapshot1"
40 |      *   indices "my-index", "my-other-index"
41 |      *   settings {
42 |      *      // ...
43 |      *   }
44 |      * }.actionGet()
45 |      * 
46 | * 47 | * @param self The {@code this} reference for the {@link CreateSnapshotRequest}. 48 | * @param settings The snapshot settings 49 | * @return Always {@code self}. 50 | * @throws NullPointerException if any parameter is {@code null} 51 | */ 52 | static CreateSnapshotRequest settings(CreateSnapshotRequest self, Closure settings) { 53 | self.settings(settings.asMap()) 54 | } 55 | 56 | /** 57 | * Sets the {@code settings} for the snapshot. 58 | * 59 | * @param self The {@code this} reference for the {@link CreateSnapshotRequestBuilder}. 60 | * @param source The snapshot settings 61 | * @return Always {@code self}. 62 | * @throws NullPointerException if any parameter is {@code null} 63 | */ 64 | static CreateSnapshotRequestBuilder setSettings(CreateSnapshotRequestBuilder self, Closure settings) { 65 | self.setSettings(settings.asMap()) 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.cluster.snapshots.restore 20 | 21 | import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest 22 | import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequestBuilder 23 | import org.elasticsearch.client.ClusterAdminClient 24 | 25 | /** 26 | * {@code RestoreSnapshotRequestExtensions} provides Groovy-friendly {@link RestoreSnapshotRequest} extensions. 27 | *

28 | * Note: This extension intentionally does _not_ provide a {@code Closure} overload of 29 | * {@link RestoreSnapshotRequest#source(java.util.Map)} because it is interpreted the same as using 30 | * {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#with} and a {@code Closure}. 31 | * @see ClusterAdminClient 32 | */ 33 | class RestoreSnapshotRequestExtensions { 34 | /** 35 | * Sets the {@code settings} for the snapshot. 36 | *

37 |      * RestoreSnapshotResponse response = client.admin.cluster.restoreSnapshot {
38 |      *   repository "snapshot_repo"
39 |      *   snapshot "snapshot1"
40 |      *   settings {
41 |      *      // ...
42 |      *   }
43 |      * }.actionGet()
44 |      * 
45 | * 46 | * @param self The {@code this} reference for the {@link RestoreSnapshotRequest}. 47 | * @param settings The snapshot settings 48 | * @return Always {@code self}. 49 | * @throws NullPointerException if any parameter is {@code null} 50 | */ 51 | static RestoreSnapshotRequest settings(RestoreSnapshotRequest self, Closure settings) { 52 | self.settings(settings.asMap()) 53 | } 54 | 55 | /** 56 | * Sets the {@code settings} for the snapshot. 57 | * 58 | * @param self The {@code this} reference for the {@link RestoreSnapshotRequestBuilder}. 59 | * @param source The snapshot settings 60 | * @return Always {@code self}. 61 | * @throws NullPointerException if any parameter is {@code null} 62 | */ 63 | static RestoreSnapshotRequestBuilder setSettings(RestoreSnapshotRequestBuilder self, Closure settings) { 64 | self.setSettings(settings.asMap()) 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/cluster/storedscripts/PutStoredScriptRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.cluster.storedscripts 20 | 21 | import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest 22 | import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequestBuilder 23 | import org.elasticsearch.client.ClusterAdminClient 24 | import org.elasticsearch.common.xcontent.XContentType 25 | 26 | /** 27 | * {@code PutStoredScriptRequestExtensions} provides Groovy-friendly {@link PutStoredScriptRequest} extensions. 28 | * @see ClusterAdminClient#putStoredScript(PutStoredScriptRequest) 29 | */ 30 | class PutStoredScriptRequestExtensions { 31 | /** 32 | * Sets the content {@code source} (script) to index. Note: The script is a string that needs to be executed on the server, not 33 | * on your client! 34 | *
35 |      * putStoredScriptRequest.script {
36 |      *   script = "Math.max(doc['field'].value)"
37 |      * }
38 |      * 
39 | * 40 | * @param self The {@code this} reference for the {@link PutStoredScriptRequest}. 41 | * @param source The content source 42 | * @return Always {@code self}. 43 | * @throws NullPointerException if any parameter is {@code null} 44 | */ 45 | static PutStoredScriptRequest script(PutStoredScriptRequest self, Closure source) { 46 | self.script(source.build(XContentType.JSON).bytes()) 47 | } 48 | 49 | /** 50 | * Sets the content {@code source} (script) to index. 51 | * 52 | * @param self The {@code this} reference for the {@link PutStoredScriptRequestBuilder}. 53 | * @param source The content source 54 | * @return Always {@code self}. 55 | * @throws NullPointerException if any parameter is {@code null} 56 | */ 57 | static PutStoredScriptRequestBuilder setSource(PutStoredScriptRequestBuilder self, Closure source) { 58 | self.setSource(source.build(XContentType.JSON).bytes()) 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/indices/create/CreateIndexRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.indices.create 20 | 21 | import org.elasticsearch.action.admin.indices.create.CreateIndexRequest 22 | import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder 23 | import org.elasticsearch.client.IndicesAdminClient 24 | 25 | /** 26 | * {@code CreateIndexRequestExtensions} provides Groovy-friendly {@link CreateIndexRequest} extensions. 27 | * @see IndicesAdminClient#create(CreateIndexRequest) 28 | */ 29 | class CreateIndexRequestExtensions { 30 | /** 31 | * Sets the index settings and mappings as a single {@code source}. 32 | * 33 | * @param self The {@code this} reference for the {@link CreateIndexRequest}. 34 | * @param source The content source 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static CreateIndexRequest source(CreateIndexRequest self, Closure source) { 39 | self.source(source.asMap()) 40 | } 41 | 42 | /** 43 | * Sets the index {@code settings}. 44 | * 45 | * @param self The {@code this} reference for the {@link CreateIndexRequest}. 46 | * @param settings The index settings 47 | * @return Always {@code self}. 48 | * @throws NullPointerException if any parameter is {@code null} 49 | */ 50 | static CreateIndexRequest settings(CreateIndexRequest self, Closure settings) { 51 | self.settings(settings.asMap()) 52 | } 53 | 54 | /** 55 | * Sets the index {@code type} and its {@code mapping}. 56 | * 57 | * @param self The {@code this} reference for the {@link CreateIndexRequest}. 58 | * @param type The type to create the {@code mapping} 59 | * @param mapping The {@code type} mapping 60 | * @return Always {@code self}. 61 | * @throws NullPointerException if any parameter is {@code null} except {@code type} 62 | */ 63 | static CreateIndexRequest mapping(CreateIndexRequest self, String type, Closure mapping) { 64 | self.mapping(type, mapping.asMap()) 65 | } 66 | 67 | /** 68 | * Sets the index settings and mappings as a single {@code source}. 69 | * 70 | * @param self The {@code this} reference for the {@link CreateIndexRequestBuilder}. 71 | * @param source The content source 72 | * @return Always {@code self}. 73 | * @throws NullPointerException if any parameter is {@code null} 74 | */ 75 | static CreateIndexRequestBuilder setSource(CreateIndexRequestBuilder self, Closure source) { 76 | self.setSource(source.asMap()) 77 | } 78 | 79 | /** 80 | * Sets the index {@code settings}. 81 | * 82 | * @param self The {@code this} reference for the {@link CreateIndexRequestBuilder}. 83 | * @param settings The index settings 84 | * @return Always {@code self}. 85 | * @throws NullPointerException if any parameter is {@code null} 86 | */ 87 | static CreateIndexRequestBuilder setSettings(CreateIndexRequestBuilder self, Closure settings) { 88 | self.setSettings(settings.asMap()) 89 | } 90 | 91 | /** 92 | * Adds the index {@code type} and its {@code mapping}. 93 | * 94 | * @param self The {@code this} reference for the {@link CreateIndexRequestBuilder}. 95 | * @param type The type to create the {@code mapping} 96 | * @param mapping The {@code type} mapping 97 | * @return Always {@code self}. 98 | * @throws NullPointerException if any parameter is {@code null} 99 | */ 100 | static CreateIndexRequestBuilder addMapping(CreateIndexRequestBuilder self, String type, Closure mapping) { 101 | self.addMapping(type, mapping.asMap()) 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/indices/mapping/put/PutMappingRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.indices.mapping.put 20 | 21 | import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest 22 | import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder 23 | import org.elasticsearch.client.IndicesAdminClient 24 | 25 | /** 26 | * {@code PutMappingRequestExtensions} provides Groovy-friendly {@link PutMappingRequest} extensions. 27 | * @see IndicesAdminClient#putMapping(PutMappingRequest) 28 | */ 29 | class PutMappingRequestExtensions { 30 | /** 31 | * Sets the mapping {@code source}. 32 | * 33 | * @param self The {@code this} reference for the {@link PutMappingRequest}. 34 | * @param source The content source 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static PutMappingRequest source(PutMappingRequest self, Closure source) { 39 | self.source(source.asJsonString()) 40 | } 41 | 42 | /** 43 | * Sets the mapping {@code source}. 44 | * 45 | * @param self The {@code this} reference for the {@link PutMappingRequestBuilder}. 46 | * @param source The content source 47 | * @return Always {@code self}. 48 | * @throws NullPointerException if any parameter is {@code null} 49 | */ 50 | static PutMappingRequestBuilder setSource(PutMappingRequestBuilder self, Closure source) { 51 | self.setSource(source.asJsonString()) 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/admin/indices/settings/put/UpdateSettingsRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.admin.indices.settings.put 20 | 21 | import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest 22 | import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequestBuilder 23 | import org.elasticsearch.client.IndicesAdminClient 24 | 25 | /** 26 | * {@code UpdateSettingsRequestExtensions} provides Groovy-friendly {@link UpdateSettingsRequest} extensions. 27 | * @see IndicesAdminClient#updateSettings(UpdateSettingsRequest) 28 | */ 29 | class UpdateSettingsRequestExtensions { 30 | /** 31 | * Sets the {@code settings} to update. 32 | * 33 | * @param self The {@code this} reference for the {@link UpdateSettingsRequest}. 34 | * @param settings The settings to update 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static UpdateSettingsRequest settings(UpdateSettingsRequest self, Closure settings) { 39 | self.settings(settings.asJsonString()) 40 | } 41 | 42 | /** 43 | * Sets the {@code settings} to update. 44 | * 45 | * @param self The {@code this} reference for the {@link UpdateSettingsRequestBuilder}. 46 | * @param settings The settings to update 47 | * @return Always {@code self}. 48 | * @throws NullPointerException if any parameter is {@code null} 49 | */ 50 | static UpdateSettingsRequestBuilder setSettings(UpdateSettingsRequestBuilder self, Closure settings) { 51 | self.setSettings(settings.asJsonString()) 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/explain/ExplainRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.explain 20 | 21 | import org.elasticsearch.action.explain.ExplainRequest 22 | import org.elasticsearch.action.explain.ExplainRequestBuilder 23 | import org.elasticsearch.client.Client 24 | import org.elasticsearch.client.Requests 25 | 26 | /** 27 | * {@code ExplainRequestExtensions} provides Groovy-friendly {@link ExplainRequest} extensions. 28 | * @see Client#explain(ExplainRequest) 29 | */ 30 | class ExplainRequestExtensions { 31 | /** 32 | * Sets the content query {@code source} to explain. 33 | * 34 | * @param self The {@code this} reference for the {@link ExplainRequest}. 35 | * @param source The content source 36 | * @return Always {@code self}. 37 | * @throws NullPointerException if any parameter is {@code null} 38 | */ 39 | static ExplainRequest source(ExplainRequest self, Closure source) { 40 | self.source(source.build(Requests.CONTENT_TYPE).bytes()) 41 | } 42 | 43 | /** 44 | * Sets the content query {@code source} to explain. 45 | * 46 | * @param self The {@code this} reference for the {@link ExplainRequestBuilder}. 47 | * @param source The content source 48 | * @return Always {@code self}. 49 | * @throws NullPointerException if any parameter is {@code null} 50 | */ 51 | static ExplainRequestBuilder setSource(ExplainRequestBuilder self, Closure source) { 52 | self.setSource(source.build(Requests.CONTENT_TYPE).bytes()) 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/fieldstats/FieldStatsRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.fieldstats 20 | 21 | import org.elasticsearch.action.fieldstats.FieldStatsRequest 22 | import org.elasticsearch.client.Client 23 | import org.elasticsearch.client.Requests 24 | 25 | /** 26 | * {@code FieldStatsRequestExtensions} provides Groovy-friendly {@link FieldStatsRequest} extensions. 27 | * @see Client#fieldStats(FieldStatsRequest) 28 | */ 29 | class FieldStatsRequestExtensions { 30 | /** 31 | * Sets the content {@code source} to handle field stats. 32 | * 33 | * @param self The {@code this} reference for the {@link FieldStatsRequest}. 34 | * @param source The content source 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static FieldStatsRequest source(FieldStatsRequest self, Closure source) { 39 | self.source(source.build(Requests.CONTENT_TYPE).bytes()) 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/index/IndexRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.index 20 | 21 | import org.elasticsearch.action.index.IndexRequest 22 | import org.elasticsearch.action.index.IndexRequestBuilder 23 | import org.elasticsearch.client.Client 24 | import org.elasticsearch.client.Requests 25 | 26 | /** 27 | * {@code IndexRequestExtensions} provides Groovy-friendly {@link IndexRequest} extensions. 28 | * @see Client#index(IndexRequest) 29 | */ 30 | class IndexRequestExtensions { 31 | /** 32 | * Sets the content {@code source} to index. 33 | * 34 | * @param self The {@code this} reference for the {@link IndexRequest}. 35 | * @param source The content source 36 | * @return Always {@code self}. 37 | * @throws NullPointerException if any parameter is {@code null} 38 | */ 39 | static IndexRequest source(IndexRequest self, Closure source) { 40 | self.source(source.buildBytes(Requests.INDEX_CONTENT_TYPE)) 41 | } 42 | 43 | /** 44 | * Sets the content {@code source} to index. 45 | * 46 | * @param self The {@code this} reference for the {@link IndexRequestBuilder}. 47 | * @param source The content source 48 | * @return Always {@code self}. 49 | * @throws NullPointerException if any parameter is {@code null} 50 | */ 51 | static IndexRequestBuilder setSource(IndexRequestBuilder self, Closure source) { 52 | self.setSource(source.buildBytes(Requests.INDEX_CONTENT_TYPE)) 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/search/SearchRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.search 20 | 21 | import org.elasticsearch.action.search.SearchRequest 22 | import org.elasticsearch.action.search.SearchRequestBuilder 23 | import org.elasticsearch.client.Client 24 | 25 | /** 26 | * {@code SearchRequestExtensions} provides Groovy-friendly {@link SearchRequest} extensions. 27 | * @see Client#search(SearchRequest) 28 | */ 29 | class SearchRequestExtensions { 30 | /** 31 | * Sets the content query {@code source}. 32 | * 33 | * @param self The {@code this} reference for the {@link SearchRequest}. 34 | * @param source The content source 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static SearchRequest source(SearchRequest self, Closure source) { 39 | self.source(source.asJsonBytes()) 40 | } 41 | 42 | /** 43 | * Sets theextra content query {@code source}. 44 | * 45 | * @param self The {@code this} reference for the {@link SearchRequest}. 46 | * @param extraSource The extra content source 47 | * @return Always {@code self}. 48 | */ 49 | static SearchRequest extraSource(SearchRequest self, Closure extraSource) { 50 | self.extraSource(extraSource.asJsonBytes()) 51 | } 52 | 53 | /** 54 | * Sets the content query {@code source}. 55 | * 56 | * @param self The {@code this} reference for the {@link SearchRequestBuilder}. 57 | * @param source The content source 58 | * @return Always {@code self}. 59 | * @throws NullPointerException if any parameter is {@code null} 60 | */ 61 | static SearchRequestBuilder setSource(SearchRequestBuilder self, Closure source) { 62 | self.setSource(source.asJsonBytes()) 63 | } 64 | 65 | /** 66 | * Sets the extra content query {@code source}. 67 | * 68 | * @param self The {@code this} reference for the {@link SearchRequestBuilder}. 69 | * @param extraSource The extra content source 70 | * @return Always {@code self}. 71 | * @throws NullPointerException if any parameter is {@code null} 72 | */ 73 | static SearchRequestBuilder setExtraSource(SearchRequestBuilder self, Closure extraSource) { 74 | self.setExtraSource(extraSource.asJsonBytes()) 75 | } 76 | 77 | /** 78 | * Sets a filter on the query executed that only applies to the search query (and not facets for example). 79 | * 80 | * @param self The {@code this} reference for the {@link SearchRequestBuilder}. 81 | * @param postFilter The post filter 82 | * @return Always {@code self}. 83 | * @throws NullPointerException if any parameter is {@code null} 84 | */ 85 | static SearchRequestBuilder setPostFilter(SearchRequestBuilder self, Closure postFilter) { 86 | self.setPostFilter(postFilter.asJsonBytes()) 87 | } 88 | 89 | /** 90 | * Constructs a new search source builder with a raw search query. 91 | *

92 | * Note: When building a new {@link SearchRequest}, using this method will overwrite other changes related to the 93 | * query. 94 | * 95 | * @param self The {@code this} reference for the {@link SearchRequestBuilder}. 96 | * @param query The search query 97 | * @return Always {@code self}. 98 | * @throws NullPointerException if any parameter is {@code null} 99 | */ 100 | static SearchRequestBuilder setQuery(SearchRequestBuilder self, Closure query) { 101 | self.setQuery(query.asJsonBytes()) 102 | } 103 | 104 | /** 105 | * Sets the aggregations to perform as part of the search. 106 | * 107 | * @param self The {@code this} reference for the {@link SearchRequestBuilder}. 108 | * @param aggregations The aggregations 109 | * @return Always {@code self}. 110 | * @throws NullPointerException if any parameter is {@code null} 111 | */ 112 | static SearchRequestBuilder setAggregations(SearchRequestBuilder self, Closure aggregations) { 113 | self.setAggregations(aggregations.asJsonBytes()) 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/action/update/UpdateRequestExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.action.update 20 | 21 | import org.elasticsearch.action.update.UpdateRequest 22 | import org.elasticsearch.action.update.UpdateRequestBuilder 23 | import org.elasticsearch.client.Client 24 | 25 | /** 26 | * {@code UpdateRequestExtensions} provides Groovy-friendly {@link UpdateRequest} extensions. 27 | * @see Client#update(UpdateRequest) 28 | */ 29 | class UpdateRequestExtensions { 30 | /** 31 | * Sets the content {@code docSource} (partial document) to use with the update. 32 | * 33 | * @param self The {@code this} reference for the {@link UpdateRequest}. 34 | * @param docSource The non-scripted, partial document source to update 35 | * @return Always {@code self}. 36 | * @throws NullPointerException if any parameter is {@code null} 37 | */ 38 | static UpdateRequest doc(UpdateRequest self, Closure docSource) throws Exception { 39 | self.doc(docSource.asMap()) 40 | } 41 | 42 | /** 43 | * Sets the content {@code source} to update. 44 | * 45 | * @param self The {@code this} reference for the {@link UpdateRequest}. 46 | * @param source The content source 47 | * @return Always {@code self}. 48 | * @throws NullPointerException if any parameter is {@code null} 49 | */ 50 | static UpdateRequest source(UpdateRequest self, Closure source) throws Exception { 51 | self.source(source.asJsonBytes()) 52 | } 53 | 54 | /** 55 | * Sets the content {@code params} to use with the update. 56 | * 57 | * @param self The {@code this} reference for the {@link UpdateRequest}. 58 | * @param params The uncached script parameters 59 | * @return Always {@code self}. 60 | * @throws NullPointerException if any parameter is {@code null} 61 | */ 62 | static UpdateRequest scriptParams(UpdateRequest self, Closure params) throws Exception { 63 | self.scriptParams(params.asMap()) 64 | } 65 | 66 | /** 67 | * Sets the content {@code source} to use in the case that the document does not exist. 68 | * 69 | * @param self The {@code this} reference for the {@link UpdateRequest}. 70 | * @param source The content source 71 | * @return Always {@code self}. 72 | * @throws NullPointerException if any parameter is {@code null} 73 | */ 74 | static UpdateRequest upsert(UpdateRequest self, Closure source) throws Exception { 75 | self.upsert(source.asMap()) 76 | } 77 | 78 | /** 79 | * Sets the content {@code params} to use with the update. 80 | * 81 | * @param self The {@code this} reference for the {@link UpdateRequestBuilder}. 82 | * @param docSource The non-scripted, partial document source to update 83 | * @return Always {@code self}. 84 | * @throws NullPointerException if any parameter is {@code null} 85 | */ 86 | static UpdateRequestBuilder setDoc(UpdateRequestBuilder self, Closure docSource) throws Exception { 87 | self.setDoc(docSource.asMap()) 88 | } 89 | 90 | /** 91 | * Sets the content {@code params} to use with the update. 92 | * 93 | * @param self The {@code this} reference for the {@link UpdateRequestBuilder}. 94 | * @param params The uncached script parameters 95 | * @return Always {@code self}. 96 | * @throws NullPointerException if any parameter is {@code null} 97 | */ 98 | static UpdateRequestBuilder setScriptParams(UpdateRequestBuilder self, Closure params) throws Exception { 99 | self.setScriptParams(params.asMap()) 100 | } 101 | 102 | /** 103 | * Sets the content {@code source} to update. 104 | * 105 | * @param self The {@code this} reference for the {@link UpdateRequestBuilder}. 106 | * @param source The content source 107 | * @return Always {@code self}. 108 | * @throws NullPointerException if any parameter is {@code null} 109 | */ 110 | static UpdateRequestBuilder setSource(UpdateRequestBuilder self, Closure source) throws Exception { 111 | self.setSource(source.asJsonBytes()) 112 | } 113 | 114 | /** 115 | * Sets the content {@code source} to update if it is actually an insert. 116 | * 117 | * @param self The {@code this} reference for the {@link UpdateRequestBuilder}. 118 | * @param source The content source 119 | * @return Always {@code self}. 120 | * @throws NullPointerException if any parameter is {@code null} 121 | */ 122 | static UpdateRequestBuilder setUpsert(UpdateRequestBuilder self, Closure source) throws Exception { 123 | self.setUpsert(source.asMap()) 124 | } 125 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/client/AbstractClientExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import groovy.transform.TypeChecked 22 | 23 | import org.elasticsearch.action.ActionRequest 24 | import org.elasticsearch.action.ActionResponse 25 | import org.elasticsearch.action.support.PlainListenableActionFuture 26 | import org.elasticsearch.client.ElasticsearchClient 27 | 28 | /** 29 | * {@code AbstractClientExtensions} provides convenience operations for {@link org.elasticsearch.client.Client} 30 | * extensions. 31 | */ 32 | @TypeChecked 33 | abstract class AbstractClientExtensions { 34 | /** 35 | * Uses the {@code request} and creates an {@link ActionResponse} using the {@code requestClosure} to generate it. 36 | * 37 | * @param client The client to perform the {@code request} 38 | * @param request The request to make 39 | * @param requestClosure The method/closure to invoke given the sole argument of {@code request}. 40 | * @return Never {@code null}. 41 | * @throws NullPointerException if any parameter is {@code null}. 42 | */ 43 | protected static 44 | Response doRequest(Client client, 47 | Request request, 48 | Closure requestClosure) { 49 | // TODO: After https://github.com/elastic/elasticsearch/issues/9201 is merged, then we can change this to avoid 50 | // unnecessary threading (and simplify this method by potentially dropping parameters)! 51 | doRequestAsync(client, request, requestClosure).actionGet() 52 | } 53 | 54 | /** 55 | * Configures the {@code request} and creates an {@link ActionResponse} using the {@code requestClosure} to 56 | * generate it. 57 | *

58 | * The {@code request} is configured using the {@code requestConfig} if it is non-{@code null}. 59 | * 60 | * @param client The client to perform the {@code request} 61 | * @param request The request to make 62 | * @param requestConfig The configuration of the {@code request} 63 | * @param requestClosure The method/closure to invoke given the sole argument of {@code request}. 64 | * @return Never {@code null}. 65 | * @throws NullPointerException if any parameter except {@code requestConfig} is {@code null}. 66 | */ 67 | protected static 68 | Response doRequest(Client client, 71 | Request request, 72 | Closure requestConfig, 73 | Closure requestClosure) { 74 | // configure the request 75 | if (requestConfig != null) { 76 | request.with(requestConfig) 77 | } 78 | 79 | doRequest(client, request, requestClosure) 80 | } 81 | 82 | /** 83 | * Uses the {@code request} and creates a {@link PlainListenableActionFuture} associated with the {@link 84 | * ActionResponse} using the {@code requestClosure} to generate it. 85 | * 86 | * @param client The client to perform the {@code request} 87 | * @param request The request to make 88 | * @param requestClosure The method/closure to invoke given the first argument of {@code request} and a 89 | * {@link PlainListenableActionFuture} as the second argument. 90 | * @return Never {@code null}. 91 | * @throws NullPointerException if any parameter is {@code null}. 92 | */ 93 | protected static 94 | PlainListenableActionFuture doRequestAsync(Client client, 97 | Request request, 98 | Closure requestClosure) { 99 | PlainListenableActionFuture responseFuture = new PlainListenableActionFuture<>(client.threadPool()) 100 | 101 | // invoke the request closure (method) that takes the request/response future to respond to 102 | requestClosure.call(request, responseFuture) 103 | 104 | responseFuture 105 | } 106 | 107 | /** 108 | * Configures the {@code request} and creates a {@link PlainListenableActionFuture} associated with the {@link 109 | * ActionResponse} using the {@code requestClosure} to generate it. 110 | *

111 | * The {@code request} is configured using the {@code requestConfig} if it is non-{@code null}. 112 | * 113 | * @param client The client to perform the {@code request} 114 | * @param request The request to make 115 | * @param requestConfig The configuration of the {@code request} 116 | * @param requestClosure The method/closure to invoke given the first argument of {@code request} and a 117 | * {@link PlainListenableActionFuture} as the second argument. 118 | * @return Never {@code null}. 119 | * @throws NullPointerException if any parameter except {@code requestConfig} is {@code null}. 120 | */ 121 | protected static 122 | PlainListenableActionFuture doRequestAsync(Client client, 125 | Request request, 126 | Closure requestConfig, 127 | Closure requestClosure) { 128 | // configure the request 129 | if (requestConfig != null) { 130 | request.with(requestConfig) 131 | } 132 | 133 | doRequestAsync(client, request, requestClosure) 134 | } 135 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/client/AdminClientExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import groovy.transform.CompileStatic 22 | import groovy.transform.TypeChecked 23 | 24 | import org.elasticsearch.client.AdminClient 25 | import org.elasticsearch.client.Client 26 | import org.elasticsearch.client.ClusterAdminClient 27 | import org.elasticsearch.client.IndicesAdminClient 28 | 29 | /** 30 | * {@code AdminClientExtensions} provides Groovy-friendly access to {@link AdminClient}s. 31 | * @see Client#admin() 32 | */ 33 | @CompileStatic 34 | @TypeChecked 35 | class AdminClientExtensions { 36 | 37 | /** 38 | * Get the {@link ClusterAdminClient} used to perform actions and operations against the cluster. 39 | * 40 | * @param self The {@code this} reference for the {@link AdminClientExtensions}. 41 | * @return Always {@link AdminClient#cluster()}. 42 | */ 43 | static ClusterAdminClient getCluster(AdminClient self) { 44 | self.cluster() 45 | } 46 | 47 | /** 48 | * Get the {@link IndicesAdminClient} used to perform actions and operations against the cluster. 49 | * 50 | * @param self The {@code this} reference for the {@link AdminClientExtensions}. 51 | * @return Always {@link AdminClient#indices()}. 52 | */ 53 | static IndicesAdminClient getIndices(AdminClient self) { 54 | self.indices() 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/common/settings/SettingsBuilderExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.settings 20 | 21 | import org.elasticsearch.ElasticsearchGenerationException 22 | import org.elasticsearch.common.settings.Settings 23 | import org.elasticsearch.common.settings.loader.JsonSettingsLoader 24 | 25 | /** 26 | * {@code ImmutableSettingsBuilderExtensions} provide Groovy-friendly extensions to {@link ImmutableSettings.Builder}. 27 | *

28 | * In particular, this adds the ability to specify settings in the form of a {@link Closure} in addition to existing 29 | * options. 30 | */ 31 | class SettingsBuilderExtensions { 32 | /** 33 | * Explicit settings to set. 34 | *

35 |      * Settings.Builder.builder().put {
36 |      *     node {
37 |      *         client = true
38 |      *     }
39 |      *     cluster {
40 |      *         name = 'es-cluster-name'
41 |      *     }
42 |      * }
43 |      * 
44 | * Note: This provides an advantage over the {@code Map} variant that requires a string-to-string mapping. This 45 | * will in effect create a JSON map out of the {@code settings} and then convert that to a string-to-string mapping 46 | * for you. 47 | * 48 | * @param self The {@code this} reference for the {@link Settings.Builder} 49 | * @param settings The settings specified as a {@link Closure} 50 | * @return Always {@code self}. 51 | * @throws NullPointerException if any parameter is {@code null} 52 | * @throws ElasticsearchGenerationException if the {@code settings} fail to parse as JSON 53 | */ 54 | static Settings.Builder put(Settings.Builder self, Closure settings) { 55 | try { 56 | self.put(new JsonSettingsLoader(true).load(settings.asJsonBytes())) 57 | } 58 | catch (IOException e) { 59 | throw new ElasticsearchGenerationException("Closure failed to map to valid JSON.", e) 60 | } 61 | 62 | self 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/common/settings/SettingsStaticExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.settings 20 | 21 | import groovy.transform.TypeChecked 22 | 23 | import org.elasticsearch.ElasticsearchGenerationException 24 | import org.elasticsearch.common.settings.Settings 25 | 26 | /** 27 | * {@code SettingsStaticExtensions} provide {@code static}, Groovy-friendly extensions to {@link Settings}. 28 | *

29 | * In particular, this adds the ability to specify settings in the form of a {@link Closure} when creating a 30 | * new {@link Settings#builder() Builder}. 31 | */ 32 | @TypeChecked 33 | class SettingsStaticExtensions { 34 | /** 35 | * Explicit settings to set. 36 | *

37 |      * Settings.builder {
38 |      *     cluster {
39 |      *         name = 'es-cluster-name'
40 |      *     }
41 |      * }.build()
42 |      * 
43 | * 44 | * @param selfType The {@code static} type that this method is added too (unused) 45 | * @param settings The settings specified as a {@link Closure} 46 | * @return Always a new {@link Settings.Builder} with the {@code settings} applied to it 47 | * @throws NullPointerException if {@code settings} is {@code null} 48 | * @throws ElasticsearchGenerationException if the {@code settings} fail to parse as JSON 49 | */ 50 | static Settings.Builder builder(Settings selfType, Closure settings) { 51 | Settings.builder().put(settings) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/groovy/org/elasticsearch/groovy/common/xcontent/XContentBuilderExtensions.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.xcontent 20 | 21 | import org.elasticsearch.common.bytes.BytesReference 22 | import org.elasticsearch.common.xcontent.XContentBuilder 23 | import org.elasticsearch.common.xcontent.XContentFactory 24 | import org.elasticsearch.common.xcontent.XContentGenerator 25 | import org.elasticsearch.common.xcontent.XContentType 26 | 27 | /** 28 | * {@link XContentBuilderExtensions} adds Groovy-friendly extensions to {@link XContentBuilder} as well as directly to 29 | * {@link Closure}s. 30 | *

31 | * In particular, this adds the ability to convert a {@link Closure} into a {@link Map} to a {@link XContentBuilder} to 32 | * enable effortless conversion: 33 | *

 34 |  * String json = XContentFactory.contentBuilder(XContentType.JSON).map {
 35 |  *   name = {
 36 |  *     first = "FirstName"
 37 |  *     last = "LastName"
 38 |  *   }
 39 |  *   // Not necessarily the recommended way to make a Date object:
 40 |  *   dob = new SimpleDateFormat("yyyy-MMM-dd").parse("2014-NOV-01")
 41 |  *   address = {
 42 |  *     street1 = "Street"
 43 |  *     city = "City"
 44 |  *     state = "State"
 45 |  *     country = "Country"
 46 |  *     postalCode = "12345"
 47 |  *   }
 48 |  * }.string
 49 |  * 
50 | * Using the extensions provided here, this can be reduced even further to: 51 | *
 52 |  * String json = {
 53 |  *   name = {
 54 |  *     first = "FirstName"
 55 |  *     last = "LastName"
 56 |  *   }
 57 |  *   // Not necessarily the recommended way to make a Date object:
 58 |  *   dob = new SimpleDateFormat("yyyy-MMM-dd").parse("2014-NOV-01")
 59 |  *   address = {
 60 |  *     street1 = "Street"
 61 |  *     city = "City"
 62 |  *     state = "State"
 63 |  *     country = "Country"
 64 |  *     postalCode = "12345"
 65 |  *   }
 66 |  * }.asJsonString()
 67 |  * 
68 | */ 69 | class XContentBuilderExtensions { 70 | /** 71 | * Convert the {@link Closure} into a {@code Map} and return the {@link org.elasticsearch.common.xcontent.XContentType#JSON JSON} {@code byte} 72 | * equivalent. 73 | * 74 | * @param self The {@code this} reference for the {@link Closure} 75 | * @param type The type of {@code XContent} to create in byte form 76 | * @return Never {@code null}. 77 | * @throws NullPointerException if {@code self} is {@code null} 78 | * @throws org.elasticsearch.ElasticsearchGenerationException if {@code type} is unrecognized 79 | * @throws IOException if any error occurs while mapping the {@link Closure} 80 | */ 81 | static byte[] asJsonBytes(Closure self) throws IOException { 82 | buildBytes(self, XContentType.JSON) 83 | } 84 | 85 | /** 86 | * Convert the {@link Closure} into a {@code Map} and return the {@link XContentType#JSON JSON} string equivalent. 87 | * 88 | * @param self The {@code this} reference for the {@link Closure} 89 | * @param type The type of {@code XContent} to create in byte form 90 | * @return Never blank. 91 | * @throws NullPointerException if {@code self} is {@code null} 92 | * @throws org.elasticsearch.ElasticsearchGenerationException if {@code type} is unrecognized 93 | * @throws IOException if any error occurs while mapping the {@link Closure} 94 | */ 95 | static String asJsonString(Closure self) throws IOException { 96 | buildString(self, XContentType.JSON) 97 | } 98 | 99 | /** 100 | * Create {@code XContentBuilder} with the specified {@code type} containing the {@code Map}ped form of the 101 | * {@link Closure}. 102 | * 103 | * @param self The {@code this} reference for the {@link Closure} 104 | * @param type The type of {@code XContent} to create 105 | * @return Never {@code null}. 106 | * @throws NullPointerException if {@code self} is {@code null} 107 | * @throws org.elasticsearch.ElasticsearchGenerationException if {@code type} is unrecognized 108 | * @throws IOException if any error occurs while mapping the {@link Closure} 109 | */ 110 | static XContentBuilder build(Closure self, XContentType type) throws IOException { 111 | XContentFactory.contentBuilder(type).map(self) 112 | } 113 | 114 | /** 115 | * Convert the {@link Closure} into a {@code Map}, and create {@code XContent} with the specified {@code type} as a 116 | * {@code byte[]}. 117 | * 118 | * @param self The {@code this} reference for the {@link Closure} 119 | * @param type The type of {@code XContent} to create in byte form 120 | * @return Never {@code null}. 121 | * @throws NullPointerException if {@code self} is {@code null} 122 | * @throws org.elasticsearch.ElasticsearchGenerationException if {@code type} is unrecognized 123 | * @throws IOException if any error occurs while mapping the {@link Closure} 124 | */ 125 | static byte[] buildBytes(Closure self, XContentType type) throws IOException { 126 | BytesReference.toBytes(build(self, type).bytes()) 127 | } 128 | 129 | /** 130 | * Convert the {@link Closure} into a {@code Map}, and create {@code XContent} with the specified {@code type} as a 131 | * {@code byte[]}. 132 | * 133 | * @param self The {@code this} reference for the {@link Closure} 134 | * @param type The type of {@code XContent} to create in byte form 135 | * @return Never {@code null}. 136 | * @throws NullPointerException if {@code self} is {@code null} 137 | * @throws org.elasticsearch.ElasticsearchGenerationException if {@code type} is unrecognized 138 | * @throws IOException if any error occurs while mapping the {@link Closure} 139 | */ 140 | static String buildString(Closure self, XContentType type) throws IOException { 141 | build(self, type).string() 142 | } 143 | 144 | /** 145 | * Close the {@link XContentBuilder} and get the resulting {@link BytesReference} in the preset 146 | * {@code XContentType}. 147 | * 148 | * @param self The {@code this} reference for the {@link XContentBuilder} 149 | * @return Always {@link XContentBuilder#bytes()}. 150 | * @throws NullPointerException if {@code self} is {@code null} 151 | */ 152 | static BytesReference getBytes(XContentBuilder self) { 153 | self.bytes() 154 | } 155 | 156 | /** 157 | * Get the internal {@link XContentGenerator} of the {@link XContentBuilder}. 158 | *

159 | * Note: This does not close the {@link XContentBuilder#generator()}. 160 | * 161 | * @param self The {@code this} reference for the {@link XContentBuilder} 162 | * @return Always {@link XContentBuilder#generator()}. 163 | * @throws NullPointerException if {@code self} is {@code null} 164 | */ 165 | static XContentGenerator getGenerator(XContentBuilder self) { 166 | self.generator() 167 | } 168 | 169 | /** 170 | * Close the {@link XContentBuilder} and get the result as a {@link String} in the preset {@code XContentType}. 171 | * 172 | * @param self The {@code this} reference for the {@link XContentBuilder} 173 | * @return Always {@link XContentBuilder#string()}. 174 | * @throws NullPointerException if {@code self} is {@code null} 175 | */ 176 | static String getString(XContentBuilder self) { 177 | self.string() 178 | } 179 | 180 | /** 181 | * Convert the {@code closure} into a {@link Map} and call {@link XContentBuilder#field(String, Map)}, effectively 182 | * setting the {@code name} to the defined sub-object. 183 | * 184 | * @param self The {@code this} reference for the {@link XContentBuilder} 185 | * @param closure The closure to convert to a map and add to the builder 186 | * @return Always {@code self}. 187 | * @throws NullPointerException if any parameter is {@code null} 188 | * @throws IOException if any error occurs while adding the field 189 | */ 190 | static XContentBuilder field(XContentBuilder self, String name, Closure closure) throws IOException { 191 | self.field(name, closure.asMap()) 192 | } 193 | 194 | /** 195 | * Convert the {@code closure} into a {@link Map} and {@link XContentBuilder#map} it to {@code self}. 196 | * 197 | * @param self The {@code this} reference for the {@link XContentBuilder} 198 | * @param closure The closure to convert to a map and add to the builder 199 | * @return Always {@code self}. 200 | * @throws NullPointerException if any parameter is {@code null} 201 | * @throws IOException if any error occurs while adding the map 202 | */ 203 | static XContentBuilder map(XContentBuilder self, Closure closure) throws IOException { 204 | self.map(closure.asMap()) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule: -------------------------------------------------------------------------------- 1 | # Licensed to Elasticsearch under one or more contributor 2 | # license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright 4 | # ownership. Elasticsearch licenses this file to you under 5 | # the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | # 19 | # Groovy Extension Modules take static methods and, using the type of the first argument, in-place extend that class. 20 | # In essence, this allows existing code to be augmented by adding methods to already packaged classes that did not 21 | # previously exist (including final classes, such as java.lang.String!). 22 | # 23 | # This is very similar to how C# extension methods work (the syntax is different and users do not have to import the 24 | # extension method's namespace/package or class). 25 | # 26 | moduleName = elasticsearch-groovy-module 27 | # 28 | # Version number is used to ensure that two ExtensionModules are not loaded twice (two versions can coexist!). 29 | # 30 | moduleVersion = 1.0 31 | # 32 | # Take static methods defined in these comma separated classes and add them as instance methods (in effect making the 33 | # first parameter the implicit this argument in normal class-level methods). 34 | # 35 | extensionClasses = \ 36 | org.elasticsearch.groovy.ClosureExtensions,\ 37 | org.elasticsearch.groovy.action.ListenableActionFutureExtensions,\ 38 | org.elasticsearch.groovy.action.admin.cluster.repositories.put.PutRepositoryRequestExtensions,\ 39 | org.elasticsearch.groovy.action.admin.cluster.settings.ClusterUpdateSettingsRequestExtensions,\ 40 | org.elasticsearch.groovy.action.admin.cluster.snapshots.create.CreateSnapshotRequestExtensions,\ 41 | org.elasticsearch.groovy.action.admin.cluster.snapshots.restore.RestoreSnapshotRequestExtensions,\ 42 | org.elasticsearch.groovy.action.admin.cluster.storedscripts.PutStoredScriptRequestExtensions,\ 43 | org.elasticsearch.groovy.action.admin.indices.create.CreateIndexRequestExtensions,\ 44 | org.elasticsearch.groovy.action.admin.indices.mapping.put.PutMappingRequestExtensions,\ 45 | org.elasticsearch.groovy.action.admin.indices.settings.put.UpdateSettingsRequestExtensions,\ 46 | org.elasticsearch.groovy.action.explain.ExplainRequestExtensions,\ 47 | org.elasticsearch.groovy.action.fieldstats.FieldStatsRequestExtensions,\ 48 | org.elasticsearch.groovy.action.index.IndexRequestExtensions,\ 49 | org.elasticsearch.groovy.action.percolate.PercolateRequestExtensions,\ 50 | org.elasticsearch.groovy.action.search.SearchRequestExtensions,\ 51 | org.elasticsearch.groovy.action.update.UpdateRequestExtensions,\ 52 | org.elasticsearch.groovy.client.AdminClientExtensions,\ 53 | org.elasticsearch.groovy.client.ClientExtensions,\ 54 | org.elasticsearch.groovy.client.ClusterAdminClientExtensions,\ 55 | org.elasticsearch.groovy.client.IndicesAdminClientExtensions,\ 56 | org.elasticsearch.groovy.node.NodeBuilderExtensions,\ 57 | org.elasticsearch.groovy.node.NodeExtensions,\ 58 | org.elasticsearch.groovy.common.settings.SettingsBuilderExtensions,\ 59 | org.elasticsearch.groovy.common.xcontent.XContentBuilderExtensions 60 | # 61 | # Take static methods defined in these comma separated classes and add them as static methods to the class associated 62 | # with the first method parameter, which is otherwise completely ignored. 63 | # 64 | staticExtensionClasses = \ 65 | org.elasticsearch.groovy.common.settings.SettingsStaticExtensions 66 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/AbstractESIntegTestCase.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import groovy.transform.CompileStatic 22 | 23 | import org.elasticsearch.test.ESIntegTestCase 24 | 25 | /** 26 | * The basis for all Groovy client test classes that require a running Elasticsearch cluster / client (integration 27 | * tests). 28 | * @see AbstractESTestCase 29 | */ 30 | @CompileStatic 31 | abstract class AbstractESIntegTestCase extends ESIntegTestCase { 32 | /** 33 | * Sanitize the test code to allow Groovy to cleanup after itself rather than forcing the onus onto everyone else. 34 | */ 35 | static { 36 | assert GroovyTestSanitizer.groovySanitized 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/AbstractESTestCase.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import groovy.transform.CompileStatic 22 | import org.elasticsearch.test.ESTestCase 23 | 24 | /** 25 | * The basis for all Groovy client test classes that do not require a running Elasticsearch cluster / client. 26 | * @see AbstractESIntegTestCase 27 | */ 28 | @CompileStatic 29 | abstract class AbstractESTestCase extends ESTestCase { 30 | /** 31 | * Sanitize the test code to allow Groovy to cleanup after itself rather than forcing the onus onto everyone else. 32 | */ 33 | static { 34 | assert GroovyTestSanitizer.groovySanitized 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/ClosureExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import org.junit.Test 22 | 23 | /** 24 | * Tests {@link ClosureExtensions}. 25 | *

26 | * These tests assume that {@link ClosureToMapConverter} is appropriately tested. 27 | */ 28 | class ClosureExtensionsTests extends AbstractESTestCase { 29 | @Test 30 | void testAsMap() { 31 | String firstName = randomAsciiOfLengthBetween(1, 8) 32 | String lastName = randomAsciiOfLengthBetween(1, 8) 33 | 34 | Map map = ClosureExtensions.asMap { 35 | name { 36 | first = firstName 37 | last = lastName 38 | } 39 | } 40 | 41 | assert map.name.first == firstName 42 | assert map.name.last == lastName 43 | } 44 | 45 | @Test 46 | void testExtensionModuleConfigured() { 47 | String randomName = randomAsciiOfLengthBetween(1, 8) 48 | 49 | assert { name = randomName }.asMap().name == randomName 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/ClosureToMapConverterTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import org.junit.Test 22 | 23 | import static org.elasticsearch.groovy.ClosureToMapConverter.mapClosure 24 | 25 | /** 26 | * Tests {@link ClosureToMapConverter}. 27 | */ 28 | class ClosureToMapConverterTests extends AbstractESTestCase { 29 | /** 30 | * A random {@code long} value. 31 | *

32 | * This is specifically called out as a local field to ensure that the {@link Closure} properly uses the field via 33 | * delegation. 34 | */ 35 | private long value = randomLong() 36 | 37 | /** 38 | * "Short Hand" means that we are using using the full property name rather than nesting closures. It's the 39 | * difference between 40 | *

 41 |      * {
 42 |      *   x.y.z = 123
 43 |      * }
 44 |      * 
45 | * and 46 | *
 47 |      * {
 48 |      *   x {
 49 |      *     y {
 50 |      *       z = 123
 51 |      *     }
 52 |      *   }
 53 |      * }
 54 |      * 
55 | * Using the short hand form is not allowed on the right hand side (it adds a lot of unnecessary 56 | * complexity). 57 | */ 58 | @Test(expected = IllegalArgumentException) 59 | void testShortHandOnRightHandSide_Fails() { 60 | mapClosure { 61 | x.y.z = randomInt() 62 | // NOT ALLOWED: 63 | c = x.y.z // <--- This is not allowed! 64 | } 65 | } 66 | 67 | @Test(expected = MissingPropertyException) 68 | void testShortHand_FailsWithBadOrder() { 69 | mapClosure { 70 | // Note: You define 'x' to be an int 71 | x = randomInt() 72 | // Now we try to define 'x.y.z', but 'x' already exists so it gets returned; then it tries to find 'y' as 73 | // a property of 'x', which won't exist 74 | x.y.z = randomInt() 75 | } 76 | } 77 | 78 | @Test 79 | void testShortHandValid() { 80 | Map values = [key: randomInt()] 81 | 82 | // NOTE: The keys are intentionally ordered. The testShortHand_FailsWithBadOrder test will try the other order 83 | Map map = mapClosure { 84 | a.b.c = value 85 | a = value // Proper way to reuse the value from a shorthand property 86 | 87 | b.c.d = values.key 88 | b = values.key 89 | } 90 | 91 | assert map['a.b.c'] == value 92 | assert map.a == value 93 | assert map['b.c.d'] == values.key 94 | assert map.b == values.key 95 | } 96 | 97 | @Test 98 | void testFlatMapConversion() { 99 | String string = randomAsciiOfLengthBetween(1, 16) 100 | Date now = new Date() 101 | boolean bool = randomBoolean() 102 | 103 | Map map = mapClosure { 104 | id = value 105 | name = string 106 | date = now 107 | valid = bool 108 | // ensure methods work 109 | method = randomBoolean() 110 | } 111 | 112 | assert map.id == value 113 | assert map.name == string 114 | assert map.date == now 115 | assert map.valid == bool 116 | assert map.method instanceof Boolean 117 | } 118 | 119 | @Test 120 | void testFlatNestedConversion() { 121 | String string = randomAsciiOfLengthBetween(1, 16) 122 | Date now = new Date() 123 | boolean bool = randomBoolean() 124 | Map checkMap = [key1: randomInt(), key2: [inner: randomInt()]] 125 | 126 | // This is necessary to handle fields with "." in the name 127 | Map map = mapClosure { 128 | date = now 129 | // Ensure nested field names work 130 | object1.id = value 131 | // Ensure nested-nested 132 | object2.object3.name = string 133 | // Ensure nested-nested-nested... 134 | object4.object5.object6.valid = bool 135 | // Ensure that right-hand side is parsed properly 136 | object7.key1 = checkMap.key1 137 | object8.key2 = checkMap.key2.inner 138 | } 139 | 140 | assert map.date == now 141 | // NOTE: You _must_ access the keys as strings because it does _not_ translate 142 | // otherwise 143 | assert map['object1.id'] == value 144 | assert map['object2.object3.name'] == string 145 | assert map['object4.object5.object6.valid'] == bool 146 | assert map['object7.key1'] == checkMap.key1 147 | assert map['object8.key2'] == checkMap.key2.inner 148 | 149 | } 150 | 151 | @Test 152 | void testReusedVariableMapConversion() { 153 | String string = randomAsciiOfLengthBetween(1, 16) 154 | Date now = new Date() 155 | boolean bool = randomBoolean() 156 | 157 | Closure closure = { 158 | id = value 159 | name = string 160 | date = now 161 | valid = bool 162 | } 163 | 164 | Map map = mapClosure(closure) 165 | 166 | assert map.id == value 167 | assert map.name == string 168 | assert map.date == now 169 | assert map.valid == bool 170 | 171 | value = randomLong() 172 | string = randomAsciiOfLengthBetween(1, 16) 173 | now = new Date() 174 | bool = ! bool 175 | 176 | map = mapClosure(closure) 177 | 178 | assert map.id == value 179 | assert map.name == string 180 | assert map.date == now 181 | assert map.valid == bool 182 | } 183 | 184 | @Test 185 | void testListMapConversion() { 186 | List values = [randomAsciiOfLengthBetween(1, 8), randomLong(), randomBoolean()] 187 | 188 | Map map = mapClosure { 189 | id = value 190 | list = values 191 | } 192 | 193 | assert map.id == value 194 | assert map.list == values 195 | } 196 | 197 | @Test 198 | void testNestedPropertyMapConversion() { 199 | String firstName = randomAsciiOfLengthBetween(1, 8) 200 | String lastName = randomAsciiOfLengthBetween(1, 8) 201 | Date now = new Date() 202 | List values = [randomAsciiOfLengthBetween(1, 8), randomLong(), randomBoolean()] 203 | 204 | Map map = mapClosure { 205 | id = value 206 | name = { 207 | first = firstName 208 | last = lastName 209 | } 210 | date = now 211 | list = values 212 | } 213 | 214 | assert map.id == value 215 | assert map.name instanceof Map 216 | assert map.name.first == firstName 217 | assert map.name.last == lastName 218 | assert map.date == now 219 | assert map.list == values 220 | // ensure there is no double-coverage 221 | assert map.first == null 222 | assert map.last == null 223 | } 224 | 225 | @Test 226 | void testNestedMethodMapConversion() { 227 | String firstName = randomAsciiOfLengthBetween(1, 8) 228 | String lastName = randomAsciiOfLengthBetween(1, 8) 229 | Date now = new Date() 230 | List values = [randomAsciiOfLengthBetween(1, 8), randomLong(), randomBoolean()] 231 | 232 | Map map = mapClosure { 233 | id value 234 | name { 235 | first firstName 236 | last lastName 237 | } 238 | date now 239 | list values 240 | } 241 | 242 | assert map.id == value 243 | assert map.name instanceof Map 244 | assert map.name.first == firstName 245 | assert map.name.last == lastName 246 | assert map.date == now 247 | assert map.list == values 248 | // ensure there is no double-coverage 249 | assert map.first == null 250 | assert map.last == null 251 | } 252 | 253 | @Test 254 | void testComplexMapConversion() { 255 | String firstName = randomAsciiOfLengthBetween(1, 8) 256 | String lastName = randomAsciiOfLengthBetween(1, 8) 257 | Date start = new Date(randomLong()) 258 | Date now = new Date() 259 | double number = randomDouble() 260 | List methodValues = [randomInt(), randomFloat()] 261 | List values = [randomAsciiOfLengthBetween(1, 8), randomLong(), randomBoolean()] 262 | int[] ints = [randomInt(), randomInt()] as int[] 263 | Map mapValues = [key : randomInt()] 264 | 265 | Set setValues = [] as Set 266 | int setSize = randomInt(8) 267 | 268 | // random number of values 269 | for (int i = 0; i < setSize; ++i) { 270 | setValues.add(randomDouble()) 271 | } 272 | 273 | Map map = mapClosure { 274 | id = value 275 | user { 276 | name { 277 | first = firstName 278 | middle = [randomAsciiOfLengthBetween(1, 8), randomAsciiOfLengthBetween(1, 8)] 279 | last = lastName 280 | } 281 | dates = { 282 | // same fieldname as name 283 | first = start 284 | } 285 | } 286 | percent = number 287 | collections { 288 | list = values 289 | map = mapValues 290 | // will be converted to a list: 291 | set = setValues 292 | } 293 | timestamp = now 294 | // single method invocation with two parameters: 295 | method methodValues[0], methodValues[1] 296 | array randomLong(), randomDouble() 297 | intArray ints 298 | } 299 | 300 | assert map.id == value 301 | assert map.user.name.first == firstName 302 | assert map.user.name.middle.size() == 2 303 | assert map.user.name.last == lastName 304 | assert map.user.dates.first == start 305 | assert map.percent == number 306 | assert map.collections.list == values 307 | assert map.collections.map == mapValues 308 | assert map.collections.set == setValues.collect() 309 | assert map.timestamp == now 310 | assert map.method == methodValues 311 | assert map.array instanceof List 312 | assert map.array.size() == 2 313 | assert map.intArray instanceof List 314 | assert map.intArray == ints as List 315 | // observed while developing, so this ensures that it isn't being defined 316 | assert map.user.randomAsciiOfLengthBetween == null 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/GroovyTestSanitizer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy 20 | 21 | import groovy.transform.CompileStatic 22 | import groovy.transform.TypeChecked 23 | 24 | import org.apache.lucene.util.LuceneTestCase 25 | import org.codehaus.groovy.reflection.ClassInfo 26 | 27 | import org.junit.Ignore 28 | 29 | import java.lang.ref.SoftReference 30 | import java.lang.reflect.Field 31 | import java.lang.reflect.Modifier 32 | 33 | /** 34 | * {@code GroovyTestSanitizer} sanitizes the existing test infrastructure to work well with the Groovy runtime. 35 | *
    36 | *
  • Modifies {@link LuceneTestCase} to add Groovy's {@link ClassInfo} type to the list (literally it's a 37 | * {@code Set}) of types that are ignored during the cleanup process.
  • 38 | *
  • This also adds the {@code SoftReference} to the list of types are ignored so that we can continue to support 39 | * the Grails release.
  • 40 | *
41 | * This should be invoked in a {@code static} block of every abstract test that uses the Elasticsearch test framework. 42 | * Since all tests are expected to extend either {@code ESTestCase} or {@code ESIntegTestCase}, 43 | * {@link AbstractESTestCase} and {@link AbstractESIntegTestCase} exist to do this for you. 44 | *
 45 |  * static {
 46 |  *     assert GroovyTestSanitizer.groovySanitized
 47 |  * }
 48 |  * 
49 | * In particular, this should be done before any test has the chance to run. 50 | * @see AbstractESTestCase 51 | * @see AbstractESIntegTestCase 52 | */ 53 | @Ignore 54 | @CompileStatic 55 | @TypeChecked 56 | class GroovyTestSanitizer { 57 | /** 58 | * This will thread safely trigger any necessary action to appropriately ignore {@code static} Groovy baggage 59 | * during test cleanup. This is necessary to avoid test failures due to automatic static field monitoring for 60 | * improper test code cleanup (and therefore JVM waste and theoretically slower tests). 61 | *
 62 |      * static {
 63 |      *     assert GroovyTestSanitizer.groovySanitized
 64 |      * }
 65 |      * 
66 | * 67 | * @return {@code true} to indicate that tests can be safely executed. Expected to always be {@code true}. 68 | */ 69 | static boolean isGroovySanitized() { 70 | // This forces the class loader to load the inner type, thereby triggering its static block. 71 | // The JVM guarantees that this will only happen once, so we can lock-free do this as many times as we want 72 | // and the static block will only ever be executed once. 73 | return GroovyTestSanitizerSingleton.SANITIZED 74 | } 75 | 76 | /** 77 | * An inner class is used to force the JVM to perform its Classloader magic. This allows us to use it as though 78 | * it's locked without ever using locks ourselves. 79 | *

80 | * It can be used as many times as necessary, but it will only be invoked once. 81 | */ 82 | private static class GroovyTestSanitizerSingleton { 83 | /** 84 | * Currently, this modifies {@link LuceneTestCase} to add {@link ClassInfo} to the set of known 85 | * static leak types to be ignored. 86 | */ 87 | static { 88 | // Types of static fields that are added by Groovy at runtime 89 | // - SoftReferences are the $callSiteArrays and they should be removed from the safe list once we only 90 | // support running with invokedynamic support 91 | Set safeGroovyTypes = new HashSet([ClassInfo.class.name, SoftReference.class.name]) 92 | 93 | // this corresponds to a Set 94 | Field field = LuceneTestCase.class.getDeclaredField('STATIC_LEAK_IGNORED_TYPES') 95 | 96 | // the field is private static, so this allows us to mess with it 97 | field.accessible = true 98 | 99 | // the field is also final, so we need to remove that 100 | Field modifiersField = Field.class.getDeclaredField("modifiers") 101 | modifiersField.accessible = true 102 | modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL) 103 | 104 | // read the field 105 | safeGroovyTypes.addAll((Set)field.get(null)) 106 | 107 | // replace the field after adding our own classes to it 108 | field.set(null, Collections.unmodifiableSet(safeGroovyTypes)) 109 | 110 | // reset it as final 111 | modifiersField.setInt(field, field.getModifiers() | Modifier.FINAL) 112 | modifiersField.accessible = false 113 | 114 | // reset it as private 115 | field.accessible = false 116 | } 117 | 118 | /** 119 | * An arbitrary field to allow the outer class to easily trigger the {@code static} block. 120 | */ 121 | static final boolean SANITIZED = true 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/client/AbstractClientTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import org.elasticsearch.action.bulk.BulkResponse 22 | import org.elasticsearch.action.index.IndexRequest 23 | import org.elasticsearch.action.index.IndexResponse 24 | import org.elasticsearch.client.Client 25 | import org.elasticsearch.client.Requests 26 | import org.elasticsearch.groovy.AbstractESIntegTestCase 27 | 28 | import org.junit.Before 29 | 30 | /** 31 | * {@code AbstractClientTests} provides helper functionality to tests that make use of {@code Client}s to perform 32 | * actions. 33 | */ 34 | abstract class AbstractClientTests extends AbstractESIntegTestCase { 35 | /** 36 | * Test {@link Client} used to process requests. 37 | */ 38 | Client client 39 | 40 | @Before 41 | void setupClient() { 42 | client = client() 43 | } 44 | 45 | /** 46 | * Index the {@code doc} in the {@code indexName} and {@code typeName} with a randomly generated ID that is 47 | * returned. 48 | * 49 | * @param indexName The name of the index. 50 | * @param typeName The name of the type. 51 | * @param doc The indexed source. 52 | * @return The randomly generated ID used to index the {@code doc}. Never {@code null}. 53 | */ 54 | String indexDoc(String indexName, String typeName, Closure doc) { 55 | String docId = randomInt() 56 | 57 | IndexResponse indexResponse = client.indexSync { 58 | index indexName 59 | type typeName 60 | id docId 61 | source doc 62 | } 63 | 64 | // sanity check 65 | assert indexResponse.id == docId 66 | 67 | docId 68 | } 69 | 70 | /** 71 | * Create {@link IndexRequest}s configured by {@code DefaultGroovyMethods.with}. 72 | *

73 | * This should look something like: 74 | *

 75 |      * bulkIndex([{
 76 |      *   index "index1"
 77 |      *   type "type1"
 78 |      *   id "id2"
 79 |      *   source {
 80 |      *     user = "kimchy"
 81 |      *   }
 82 |      * }, { // <-- note the comma
 83 |      *   index "index2"
 84 |      *   type "type2"
 85 |      *   id "id1"
 86 |      *   source {
 87 |      *     user = "other"
 88 |      *   }
 89 |      * }])
 90 |      * 
91 | * This method will always refresh the index by the {@code indexName} to ensure that the documents are 92 | * immediately searchable. 93 | * 94 | * @param indexConfigs The configuration of individual {@link IndexRequest}s. 95 | * @return Never {@code null}. 96 | * @throws IllegalArgumentException if any of the {@code indexConfigs} call invoke invalid methods. 97 | */ 98 | BulkResponse bulkIndex(List indexConfigs) { 99 | bulkIndex(null, indexConfigs) 100 | } 101 | 102 | /** 103 | * Create {@link IndexRequest}s configured by {@code DefaultGroovyMethods.with}, all going to the {@code indexName}. 104 | *

105 | * This should look something like: 106 | *

107 |      * bulkIndex("index1", [{
108 |      *   type "type1"
109 |      *   id "id2"
110 |      *   source {
111 |      *     user = "kimchy"
112 |      *   }
113 |      * }, { // <-- note the comma
114 |      *   index "index2" // <-- note this will override the passed in index for this request only
115 |      *   type "type2"
116 |      *   id "id1"
117 |      *   source {
118 |      *     user = "other"
119 |      *   }
120 |      * }])
121 |      * 
122 | * This method will always refresh the index by the {@code indexName} to ensure that the documents are 123 | * immediately searchable. 124 | * 125 | * @param indexName The name of the index. This value can be overridden by each configuration per-request. 126 | * @param indexConfigs The configuration of individual {@link IndexRequest}s. 127 | * @return Never {@code null}. 128 | * @throws IllegalArgumentException if any of the {@code indexConfigs} call invoke invalid methods. 129 | */ 130 | BulkResponse bulkIndex(String indexName, List indexConfigs) { 131 | BulkResponse bulkResponse = client.bulkSync { 132 | // note: this adds a List 133 | add indexConfigs.collect { 134 | Requests.indexRequest(indexName).with(it) 135 | } 136 | } 137 | 138 | // sanity check 139 | assert ! bulkResponse.hasFailures() 140 | 141 | // refresh the index to guarantee searchability 142 | assert client.admin.indices.refreshSync { indices indexName }.failedShards == 0 143 | 144 | bulkResponse 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/client/AdminClientExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import org.elasticsearch.client.AdminClient 22 | import org.elasticsearch.client.ClusterAdminClient 23 | import org.elasticsearch.client.IndicesAdminClient 24 | import org.elasticsearch.groovy.AbstractESTestCase 25 | 26 | import org.junit.Test 27 | 28 | import static org.mockito.Mockito.mock 29 | import static org.mockito.Mockito.verifyZeroInteractions 30 | import static org.mockito.Mockito.when 31 | 32 | /** 33 | * Tests {@link AdminClientExtensions}. 34 | */ 35 | class AdminClientExtensionsTests extends AbstractESTestCase { 36 | /** 37 | * Mock {@link AdminClient} to ensure functionality. 38 | */ 39 | private final AdminClient admin = mock(AdminClient) 40 | 41 | @Test 42 | void testGetCluster() { 43 | ClusterAdminClient cluster = mock(ClusterAdminClient) 44 | 45 | when(admin.cluster()).thenReturn(cluster) 46 | 47 | assert AdminClientExtensions.getCluster(admin) == cluster 48 | 49 | verifyZeroInteractions(cluster) 50 | } 51 | 52 | @Test 53 | void testGetIndices() { 54 | IndicesAdminClient indices = mock(IndicesAdminClient) 55 | 56 | when(admin.indices()).thenReturn(indices) 57 | 58 | assert AdminClientExtensions.getIndices(admin) == indices 59 | 60 | verifyZeroInteractions(indices) 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/client/ClientExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import org.elasticsearch.client.AdminClient 22 | import org.elasticsearch.client.Client 23 | import org.elasticsearch.common.settings.Settings 24 | import org.elasticsearch.groovy.AbstractESTestCase 25 | 26 | import org.junit.Test 27 | 28 | import static org.mockito.Mockito.mock 29 | import static org.mockito.Mockito.verifyZeroInteractions 30 | import static org.mockito.Mockito.when 31 | 32 | /** 33 | * Tests {@link ClientExtensions}. 34 | */ 35 | class ClientExtensionsTests extends AbstractESTestCase { 36 | /** 37 | * Mock {@link Client} to ensure functionality. 38 | */ 39 | private final Client client = mock(Client) 40 | 41 | @Test 42 | void testGetAdmin() { 43 | AdminClient admin = mock(AdminClient) 44 | 45 | when(client.admin()).thenReturn(admin) 46 | 47 | assert ClientExtensions.getAdmin(client) == admin 48 | 49 | verifyZeroInteractions(admin) 50 | } 51 | 52 | @Test 53 | void testGetIndices() { 54 | Settings settings = Settings.EMPTY 55 | 56 | when(client.settings()).thenReturn(settings) 57 | 58 | assert ClientExtensions.getSettings(client) == settings 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/client/IndicesAdminClientExtensionsActionTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.client 20 | 21 | import org.apache.lucene.util.LuceneTestCase.BadApple 22 | 23 | import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse 24 | import org.elasticsearch.action.admin.indices.refresh.RefreshResponse 25 | import org.elasticsearch.action.search.SearchResponse 26 | import org.elasticsearch.client.IndicesAdminClient 27 | import org.elasticsearch.cluster.metadata.MappingMetaData 28 | 29 | import org.junit.Before 30 | import org.junit.Test 31 | 32 | /** 33 | * Tests {@code ActionRequest}s added by {@link IndicesAdminClientExtensions}. 34 | */ 35 | class IndicesAdminClientExtensionsActionTests extends AbstractClientTests { 36 | /** 37 | * The index to use for most tests. 38 | */ 39 | String indexName = 'indices' 40 | /** 41 | * The index type to use for most tests. 42 | */ 43 | String typeName = 'actions' 44 | 45 | /** 46 | * Enhanced {@link IndicesAdminClient} that is tested alongside relevant {@link #client} actions. 47 | */ 48 | IndicesAdminClient indicesAdminClient 49 | 50 | @Before 51 | void setupAdmin() { 52 | indicesAdminClient = client.admin.indices 53 | } 54 | 55 | @Test 56 | void testRefreshRequestSync() { 57 | String docId = indexDoc(indexName, typeName) { 58 | name = "needle" 59 | } 60 | 61 | // refresh the index to guarantee searchability 62 | RefreshResponse response = indicesAdminClient.refreshSync { 63 | indices indexName 64 | } 65 | 66 | assert response.failedShards == 0 67 | 68 | // because we have refreshed the index, we should now be able to search for documents guaranteed 69 | SearchResponse searchResponse = client.searchSync { 70 | indices indexName 71 | types typeName 72 | source { 73 | query { 74 | match { 75 | name = "needle" 76 | } 77 | } 78 | } 79 | } 80 | 81 | assert searchResponse.hits.totalHits == 1 82 | assert searchResponse.hits.hits[0].id == docId 83 | } 84 | 85 | @Test 86 | void testRefreshRequest() { 87 | String docId = indexDoc(indexName, typeName) { 88 | name = "needle" 89 | } 90 | 91 | // refresh the index to guarantee searchability 92 | RefreshResponse response = indicesAdminClient.refresh { 93 | indices indexName 94 | }.actionGet() 95 | 96 | assert response.failedShards == 0 97 | 98 | // because we have refreshed the index, we should now be able to search for documents guaranteed 99 | SearchResponse searchResponse = client.search { 100 | indices indexName 101 | types typeName 102 | source { 103 | query { 104 | match { 105 | name = "needle" 106 | } 107 | } 108 | } 109 | }.actionGet() 110 | 111 | assert searchResponse.hits.totalHits == 1 112 | assert searchResponse.hits.hits[0].id == docId 113 | } 114 | 115 | @Test 116 | void testRefreshRequestAsync() { 117 | String docId = indexDoc(indexName, typeName) { 118 | name = "needle" 119 | } 120 | 121 | // refresh the index to guarantee searchability 122 | RefreshResponse response = indicesAdminClient.refreshAsync { 123 | indices indexName 124 | }.actionGet() 125 | 126 | assert response.failedShards == 0 127 | 128 | // because we have refreshed the index, we should now be able to search for documents guaranteed 129 | SearchResponse searchResponse = client.searchAsync { 130 | indices indexName 131 | types typeName 132 | source { 133 | query { 134 | match { 135 | name = "needle" 136 | } 137 | } 138 | } 139 | }.actionGet() 140 | 141 | assert searchResponse.hits.totalHits == 1 142 | assert searchResponse.hits.hits[0].id == docId 143 | } 144 | 145 | @Test 146 | void testGetMappingRequestSync() { 147 | // index a document to guarantee that a mapping exists 148 | indexDoc(indexName, typeName) { 149 | name = "needle" 150 | } 151 | 152 | // ensure that the mapping exists 153 | // refresh the index to guarantee searchability 154 | RefreshResponse refreshResponse = indicesAdminClient.refreshSync { 155 | indices indexName 156 | } 157 | 158 | assert refreshResponse.failedShards == 0 159 | 160 | GetMappingsResponse response = indicesAdminClient.getMappingsSync { 161 | indices indexName 162 | } 163 | 164 | // extra the mapping for the current index/type 165 | MappingMetaData mappingMetaData = response.mappings.get(indexName).get(typeName) 166 | 167 | // ensure that we properly mapped the 'name' field to a string 168 | assert mappingMetaData.sourceAsMap['properties']['name']['type'] == 'string' 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/common/settings/SettingsBuilderExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.settings 20 | 21 | import org.elasticsearch.common.settings.Settings 22 | import org.elasticsearch.groovy.AbstractESTestCase 23 | 24 | import org.junit.Test 25 | 26 | /** 27 | * Tests {@link SettingsBuilderExtensions}. 28 | */ 29 | class SettingsBuilderExtensionsTests extends AbstractESTestCase { 30 | /** 31 | * Tested {@link Settings.Builder}. 32 | */ 33 | private final Settings.Builder builder = Settings.builder() 34 | 35 | @Test 36 | void testSettingsClosure() { 37 | String clusterName = randomAsciiOfLengthBetween(1, 128) 38 | boolean clientNode = randomBoolean() 39 | boolean dataNode = randomBoolean() 40 | boolean localNode = randomBoolean() 41 | int arbitraryField = randomInt() 42 | 43 | SettingsBuilderExtensions.put(builder) { 44 | arbitrary { 45 | field = arbitraryField 46 | } 47 | // Tests inner field support 48 | cluster.name = clusterName 49 | node { 50 | client = clientNode 51 | data = dataNode 52 | local = localNode 53 | } 54 | } 55 | 56 | // verify that the settings were added appropriately 57 | Settings settings = builder.build() 58 | 59 | assert settings.getAsBoolean("node.client", null) == clientNode 60 | assert settings.getAsBoolean("node.data", null) == dataNode 61 | assert settings.getAsBoolean("node.local", null) == localNode 62 | assert settings.get("cluster.name") == clusterName 63 | assert settings.getAsInt("arbitrary.field", null) == arbitraryField 64 | } 65 | 66 | @Test 67 | void testExtensionModuleConfigured() { 68 | Settings.Builder mapBuilder = Settings.builder() 69 | 70 | String value = randomAsciiOfLengthBetween(16, 128) 71 | 72 | // same key/value 73 | Map settingsMap = [key : value] 74 | Closure settingsClosure = { key = value } 75 | 76 | // behave the same 77 | assert builder.put(settingsClosure) == builder 78 | assert mapBuilder.put(settingsMap) == mapBuilder 79 | 80 | // built from the closure 81 | Settings settings = builder.build() 82 | 83 | // actually defined and not null, matching the same value in the map version 84 | assert settings.get('key') != null 85 | assert settings.get('key') == mapBuilder.build().get('key') 86 | } 87 | } -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/common/settings/SettingsStaticExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.settings 20 | 21 | import org.elasticsearch.common.settings.Settings 22 | import org.elasticsearch.groovy.AbstractESTestCase 23 | 24 | import org.junit.Test 25 | 26 | /** 27 | * Tests {@link SettingsStaticExtensions}. 28 | */ 29 | class SettingsStaticExtensionsTests extends AbstractESTestCase { 30 | 31 | @Test 32 | void testSettingsClosure() { 33 | String clusterName = randomAsciiOfLengthBetween(1, 128) 34 | boolean clientNode = randomBoolean() 35 | boolean dataNode = randomBoolean() 36 | boolean localNode = randomBoolean() 37 | int arbitraryField = randomInt() 38 | 39 | // verify that the settings were added appropriately (note: first arg is unused and not shown in the actual 40 | // extension version, as shown in the final test in this test class) 41 | Settings settings = SettingsStaticExtensions.builder(null) { 42 | arbitrary { 43 | field = arbitraryField 44 | } 45 | cluster { 46 | name = clusterName 47 | } 48 | node { 49 | client = clientNode 50 | data = dataNode 51 | local = localNode 52 | } 53 | }.build() 54 | 55 | assert settings.getAsBoolean("node.client", null) == clientNode 56 | assert settings.getAsBoolean("node.data", null) == dataNode 57 | assert settings.getAsBoolean("node.local", null) == localNode 58 | assert settings.get("cluster.name") == clusterName 59 | assert settings.getAsInt("arbitrary.field", null) == arbitraryField 60 | } 61 | 62 | @Test 63 | void testExtensionModuleConfigured() { 64 | Settings.Builder mapBuilder = Settings.builder() 65 | 66 | String value = randomAsciiOfLengthBetween(16, 128) 67 | 68 | // same key/value as used in the closure 69 | mapBuilder.put([key : value]) 70 | 71 | // built from the closure 72 | Settings closureSettings = Settings.builder { 73 | key = value 74 | }.build() 75 | 76 | // actually defined and not null, matching the same value in the map version 77 | assert closureSettings.get('key') != null 78 | assert closureSettings.get('key') == mapBuilder.build().get('key') 79 | } 80 | } -------------------------------------------------------------------------------- /src/test/groovy/org/elasticsearch/groovy/common/xcontent/XContentBuilderExtensionsTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.groovy.common.xcontent 20 | 21 | import org.elasticsearch.common.bytes.BytesReference 22 | import org.elasticsearch.common.xcontent.XContentBuilder 23 | import org.elasticsearch.common.xcontent.XContentFactory 24 | import org.elasticsearch.common.xcontent.XContentParser 25 | import org.elasticsearch.common.xcontent.XContentType 26 | import org.elasticsearch.groovy.AbstractESTestCase 27 | import org.junit.After 28 | import org.junit.Test 29 | 30 | /** 31 | * Tests {@link XContentBuilderExtensions}. 32 | */ 33 | class XContentBuilderExtensionsTests extends AbstractESTestCase { 34 | /** 35 | * Get a random {@link XContentType} to ensure all types work. 36 | */ 37 | private final XContentType type = XContentType.values()[randomInt(XContentType.values().length - 1)] 38 | 39 | /** 40 | * Reproducible number. 41 | */ 42 | private final long value = randomLong() 43 | /** 44 | * Reproducible string. 45 | */ 46 | private final String nestedName = randomAsciiOfLengthBetween(1, 8) 47 | 48 | /** 49 | * {@link Closure} used to test mappings. 50 | */ 51 | private final Closure closure = { 52 | id = value 53 | nested { 54 | name = nestedName 55 | } 56 | } 57 | 58 | /** 59 | * {@link XContentBuilder} to ensure functionality. 60 | */ 61 | private final XContentBuilder jsonBuilder = XContentFactory.contentBuilder(XContentType.JSON) 62 | 63 | /** 64 | * If non-{@code null}, this will be automatically closed after testing. 65 | */ 66 | private XContentParser parser = null 67 | 68 | /** 69 | * 70 | */ 71 | @After 72 | void closeParser() { 73 | parser?.close() 74 | } 75 | 76 | @Test 77 | void testAsJsonBytes() { 78 | byte[] bytes = XContentBuilderExtensions.asJsonBytes(closure) 79 | 80 | // close the parser after it's used 81 | parser = XContentType.JSON.xContent().createParser(bytes) 82 | 83 | assert closure.asMap() == parser.map() 84 | } 85 | 86 | @Test 87 | void testAsJsonString() { 88 | String string = XContentBuilderExtensions.asJsonString(closure) 89 | 90 | // close the parser after it's used 91 | parser = XContentType.JSON.xContent().createParser(string) 92 | 93 | assert closure.asMap() == parser.map() 94 | } 95 | 96 | @Test 97 | void testBuild() { 98 | XContentBuilder builder = XContentBuilderExtensions.build(closure, type) 99 | 100 | // close the parser after it's used 101 | parser = type.xContent().createParser(builder.bytes()) 102 | 103 | assert type == builder.contentType() 104 | assert closure.asMap() == parser.map() 105 | } 106 | 107 | @Test 108 | void testBuildBytes() { 109 | byte[] bytes = XContentBuilderExtensions.buildBytes(closure, type) 110 | 111 | // close the parser after it's used 112 | parser = type.xContent().createParser(bytes) 113 | 114 | assert closure.asMap() == parser.map() 115 | } 116 | 117 | @Test 118 | void testBuildString() { 119 | // avoid using CBOR/SMILE because it will [expectedly] fail with strings (so YAML was picked explicitly to 120 | // avoid using the binary only formats and unnecessarily reusing JSON) 121 | String string = XContentBuilderExtensions.buildString(closure, XContentType.YAML) 122 | 123 | // close the parser after it's used 124 | parser = XContentType.YAML.xContent().createParser(string) 125 | 126 | assert closure.asMap() == parser.map() 127 | } 128 | 129 | @Test 130 | void testGetBytes() { 131 | assert XContentBuilderExtensions.getBytes(jsonBuilder) == jsonBuilder.bytes() 132 | } 133 | 134 | @Test 135 | void testGetGenerator() { 136 | assert XContentBuilderExtensions.getGenerator(jsonBuilder) == jsonBuilder.generator() 137 | } 138 | 139 | @Test 140 | void testGetString() { 141 | assert XContentBuilderExtensions.getString(jsonBuilder) == jsonBuilder.string() 142 | } 143 | 144 | @Test 145 | void testExtensionModuleConfigured() { 146 | // map the same closure 147 | XContentBuilder arbitraryBuilder = XContentFactory.contentBuilder(type).map(closure) 148 | XContentBuilder jsonBuilder = XContentFactory.contentBuilder(XContentType.JSON).map(closure) 149 | 150 | assert closure.asJsonString() == jsonBuilder.string() 151 | assert closure.asJsonBytes() == BytesReference.toBytes(jsonBuilder.bytes()) 152 | assert closure.buildString(type) == arbitraryBuilder.string() 153 | assert closure.build(type).string() == arbitraryBuilder.string() 154 | assert closure.buildBytes(type) == arbitraryBuilder.bytes().toBytes() 155 | assert jsonBuilder.bytes() == jsonBuilder.getBytes() 156 | assert jsonBuilder.generator() == jsonBuilder.getGenerator() 157 | assert jsonBuilder.string() == jsonBuilder.getString() 158 | } 159 | 160 | // simple tests to ensure accuracy (these are also covered in the ClosureExtension tests, though less specifically) 161 | // all of these require that the extension module be configured 162 | 163 | @Test 164 | void testSingleProperty() { 165 | assert '{"rootprop":"something"}' == { rootprop = 'something' }.asJsonString() 166 | } 167 | 168 | @Test 169 | void testArrayAndProperty() { 170 | assert '{"categories":["a","b","c"],"rootprop":"something"}' == { 171 | categories = ['a', 'b', 'c'] 172 | rootprop = 'something' 173 | }.asJsonString() 174 | } 175 | 176 | @Test 177 | void testDotObjects() { 178 | assert '{"categories":["a","b","c"],"rootprop":"something","test.subprop":10}' == { 179 | categories = ['a', 'b', 'c'] 180 | rootprop = 'something' 181 | test.subprop = 10 182 | }.asJsonString() 183 | } 184 | 185 | @Test 186 | void testNestedObjects() { 187 | assert '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10}}' == { 188 | categories = ['a', 'b', 'c'] 189 | rootprop = 'something' 190 | test { 191 | subprop = 10 192 | } 193 | }.asJsonString() 194 | } 195 | 196 | @Test 197 | void testAssignedNestedObjects() { 198 | assert '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10}}' == { 199 | categories = ['a', 'b', 'c'] 200 | rootprop = 'something' 201 | test = { 202 | subprop = 10 203 | } 204 | }.asJsonString() 205 | } 206 | 207 | @Test 208 | void testMapObjects() { 209 | assert '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10,"three":[1,2,3]}}' == { 210 | categories = ['a', 'b', 'c'] 211 | rootprop = 'something' 212 | test subprop: 10, three: [1, 2, 3] 213 | }.asJsonString() 214 | } 215 | 216 | @Test 217 | void testArrayOfClosures() { 218 | assert '{"foo":[{"bar":"hello"},{"hello":"bar"}]}' == { 219 | foo = [{ bar = 'hello'}, { hello = 'bar' }] 220 | }.asJsonString() 221 | } 222 | 223 | @Test 224 | void testExampleFromReferenceGuide() { 225 | List results = ['one', 'two', 'three'] 226 | 227 | assert '{"books":[{"title":"one"},{"title":"two"},{"title":"three"}]}' == { 228 | books = results.collect { 229 | [title: it] 230 | } 231 | }.asJsonString() 232 | } 233 | 234 | @Test 235 | void testAppendToList() { 236 | List results = ['one', 'two', 'three'] 237 | 238 | assert '{"books":[{"title":"one"},{"title":"two"},{"title":"three"}]}' == { 239 | books = [] 240 | for (b in results) { 241 | books << [title: b] 242 | } 243 | }.asJsonString() 244 | } 245 | 246 | @Test 247 | void testReusedClosure() { 248 | List results = ['one', 'two', 'three'] 249 | 250 | Closure closure = { 251 | rootprop = 'something' 252 | books = results.collect { 253 | [title: it] 254 | } 255 | } 256 | 257 | assert closure.asJsonString() == closure.asJsonString() 258 | assert '{"rootprop":"something","books":[{"title":"one"},{"title":"two"},{"title":"three"}]}' == closure.asJsonString() 259 | } 260 | } -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, out 2 | 3 | log4j.appender.out=org.apache.log4j.ConsoleAppender 4 | log4j.appender.out.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n 6 | --------------------------------------------------------------------------------