From 71e46e69470dbe0798b644e2e022ac6054b95ebe Mon Sep 17 00:00:00 2001 From: Sebastian Greve Date: Mon, 23 Sep 2024 11:45:14 +0200 Subject: [PATCH] Combined search-wrapper, search-wrapper-os and search-wrapper-os-rest --- .gitignore | 4 +- README.md | 4 +- pom.xml | 32 +- search-wrapper-api/.gitignore | 4 + search-wrapper-api/LICENSE | 373 ++++++++++++++++++ search-wrapper-api/pom.xml | 40 ++ .../search/wrapper/Conjunction.java | 0 .../search/wrapper/LinearDecayFunction.java | 0 .../search/wrapper/MultiSearchFilter.java | 0 .../search/wrapper/SearchClient.java | 0 .../search/wrapper/SearchField.java | 0 .../search/wrapper/SearchFilter.java | 0 .../search/wrapper/SearchFilterType.java | 30 +- .../search/wrapper/SearchFilterValue.java | 0 .../search/wrapper/SearchQuery.java | 0 .../search/wrapper/SearchQueryBuilder.java | 0 .../search/wrapper/SearchResult.java | 0 .../search/wrapper/SearchSorting.java | 0 .../aggregations/RangeAggregation.java | 0 .../aggregations/SearchAggregation.java | 0 .../aggregations/TermsAggregation.java | 0 .../results/AggregationResult.java | 0 .../results/AggregationResultBuilder.java | 0 .../results/AggregationResultEntry.java | 0 .../greendelta/search/wrapper/score/Case.java | 0 .../search/wrapper/score/Comparator.java | 0 .../search/wrapper/score/Condition.java | 0 .../search/wrapper/score/Field.java | 0 .../search/wrapper/score/Score.java | 0 search-wrapper-os/.gitignore | 6 + search-wrapper-os/LICENSE | 373 ++++++++++++++++++ search-wrapper-os/pom.xml | 42 ++ .../search/wrapper/os/Aggregation.java | 62 +++ .../search/wrapper/os/OsClient.java | 243 ++++++++++++ .../search/wrapper/os/OsRestClient.java | 281 +++++++++++++ .../greendelta/search/wrapper/os/Query.java | 183 +++++++++ .../greendelta/search/wrapper/os/Request.java | 56 +++ .../search/wrapper/os/Response.java | 57 +++ .../search/wrapper/os/RestRequest.java | 60 +++ .../search/wrapper/os/RestResponse.java | 60 +++ .../greendelta/search/wrapper/os/Result.java | 106 +++++ .../greendelta/search/wrapper/os/Script.java | 107 +++++ .../greendelta/search/wrapper/os/Search.java | 201 ++++++++++ 43 files changed, 2287 insertions(+), 37 deletions(-) create mode 100644 search-wrapper-api/.gitignore create mode 100644 search-wrapper-api/LICENSE create mode 100644 search-wrapper-api/pom.xml rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/Conjunction.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/LinearDecayFunction.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/MultiSearchFilter.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchClient.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchField.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchFilter.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchFilterType.java (89%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchFilterValue.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchQuery.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchQueryBuilder.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchResult.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/SearchSorting.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/RangeAggregation.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/SearchAggregation.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/TermsAggregation.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResult.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultBuilder.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultEntry.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/score/Case.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/score/Comparator.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/score/Condition.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/score/Field.java (100%) rename {src => search-wrapper-api/src}/main/java/com/greendelta/search/wrapper/score/Score.java (100%) create mode 100644 search-wrapper-os/.gitignore create mode 100644 search-wrapper-os/LICENSE create mode 100644 search-wrapper-os/pom.xml create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Aggregation.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsClient.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsRestClient.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Query.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Request.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Response.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestRequest.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestResponse.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Result.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Script.java create mode 100644 search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Search.java diff --git a/.gitignore b/.gitignore index 8bd3a05..8ad43b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -/target/ -/.settings/ -/.classpath /.project +/.settings \ No newline at end of file diff --git a/README.md b/README.md index 7328c7f..c92a19a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This project provides an abstraction layer for search engine implementation. #### Get the source code of the application We recommend that to use Git to manage the source code but you can also download -the source code as a [zip file](https://github.com/GreenDelta/search-wrapper/archive/master.zip). +the source code as a [zip file](https://github.com/GreenDelta/search-wrapper/archive/main.zip). Create a development directory (the path should not contain whitespaces): ```bash @@ -20,4 +20,4 @@ git clone https://github.com/GreenDelta/search-wrapper.git ``` #### Build -Now you can build the module with `mvn install`, which will install the module in your local maven repository. +Now you can build the module with `mvn install`, which will install the modules search-wrapper-api and search-wrapper-os in your local maven repository. diff --git a/pom.xml b/pom.xml index 40e397d..9a1d3c3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,6 +4,7 @@ com.greendelta.search search-wrapper 1.1.6 + pom UTF-8 @@ -11,6 +12,11 @@ 21 + + search-wrapper-api + search-wrapper-os + + Mozilla Public License, Version 2.0 @@ -19,22 +25,14 @@ - - - - org.codehaus.mojo - license-maven-plugin - 2.4.0 - - - download-licenses - - download-licenses - - - - - - + + + + com.greendelta.search + search-wrapper-api + ${project.version} + + + \ No newline at end of file diff --git a/search-wrapper-api/.gitignore b/search-wrapper-api/.gitignore new file mode 100644 index 0000000..8bd3a05 --- /dev/null +++ b/search-wrapper-api/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.settings/ +/.classpath +/.project diff --git a/search-wrapper-api/LICENSE b/search-wrapper-api/LICENSE new file mode 100644 index 0000000..fa0086a --- /dev/null +++ b/search-wrapper-api/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/search-wrapper-api/pom.xml b/search-wrapper-api/pom.xml new file mode 100644 index 0000000..ea43628 --- /dev/null +++ b/search-wrapper-api/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + com.greendelta.search + search-wrapper-api + 1.1.6 + + + UTF-8 + 21 + 21 + + + + + Mozilla Public License, Version 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + repo + + + + + + + org.codehaus.mojo + license-maven-plugin + 2.4.0 + + + download-licenses + + download-licenses + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/greendelta/search/wrapper/Conjunction.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/Conjunction.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/Conjunction.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/Conjunction.java diff --git a/src/main/java/com/greendelta/search/wrapper/LinearDecayFunction.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/LinearDecayFunction.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/LinearDecayFunction.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/LinearDecayFunction.java diff --git a/src/main/java/com/greendelta/search/wrapper/MultiSearchFilter.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/MultiSearchFilter.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/MultiSearchFilter.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/MultiSearchFilter.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchClient.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchClient.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchClient.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchClient.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchField.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchField.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchField.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchField.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchFilter.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilter.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchFilter.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilter.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchFilterType.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilterType.java similarity index 89% rename from src/main/java/com/greendelta/search/wrapper/SearchFilterType.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilterType.java index 6e72774..01006c8 100644 --- a/src/main/java/com/greendelta/search/wrapper/SearchFilterType.java +++ b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilterType.java @@ -1,15 +1,15 @@ -package com.greendelta.search.wrapper; - -public enum SearchFilterType { - - PHRASE, - - WILDCARD, - - RANGE, - - TERM, - - UNKNOWN; - -} +package com.greendelta.search.wrapper; + +public enum SearchFilterType { + + PHRASE, + + WILDCARD, + + RANGE, + + TERM, + + UNKNOWN; + +} diff --git a/src/main/java/com/greendelta/search/wrapper/SearchFilterValue.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilterValue.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchFilterValue.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchFilterValue.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchQuery.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchQuery.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchQuery.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchQuery.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchQueryBuilder.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchQueryBuilder.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchQueryBuilder.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchQueryBuilder.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchResult.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchResult.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchResult.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchResult.java diff --git a/src/main/java/com/greendelta/search/wrapper/SearchSorting.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchSorting.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/SearchSorting.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/SearchSorting.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/RangeAggregation.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/RangeAggregation.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/RangeAggregation.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/RangeAggregation.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/SearchAggregation.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/SearchAggregation.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/SearchAggregation.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/SearchAggregation.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/TermsAggregation.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/TermsAggregation.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/TermsAggregation.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/TermsAggregation.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResult.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResult.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResult.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResult.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultBuilder.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultBuilder.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultBuilder.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultBuilder.java diff --git a/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultEntry.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultEntry.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultEntry.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/aggregations/results/AggregationResultEntry.java diff --git a/src/main/java/com/greendelta/search/wrapper/score/Case.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Case.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/score/Case.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Case.java diff --git a/src/main/java/com/greendelta/search/wrapper/score/Comparator.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Comparator.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/score/Comparator.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Comparator.java diff --git a/src/main/java/com/greendelta/search/wrapper/score/Condition.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Condition.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/score/Condition.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Condition.java diff --git a/src/main/java/com/greendelta/search/wrapper/score/Field.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Field.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/score/Field.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Field.java diff --git a/src/main/java/com/greendelta/search/wrapper/score/Score.java b/search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Score.java similarity index 100% rename from src/main/java/com/greendelta/search/wrapper/score/Score.java rename to search-wrapper-api/src/main/java/com/greendelta/search/wrapper/score/Score.java diff --git a/search-wrapper-os/.gitignore b/search-wrapper-os/.gitignore new file mode 100644 index 0000000..437a313 --- /dev/null +++ b/search-wrapper-os/.gitignore @@ -0,0 +1,6 @@ +/.classpath +/.factorypath +/.project +/.settings/ +/bin/ +/target/ \ No newline at end of file diff --git a/search-wrapper-os/LICENSE b/search-wrapper-os/LICENSE new file mode 100644 index 0000000..fa0086a --- /dev/null +++ b/search-wrapper-os/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/search-wrapper-os/pom.xml b/search-wrapper-os/pom.xml new file mode 100644 index 0000000..806e5a2 --- /dev/null +++ b/search-wrapper-os/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + com.greendelta.search + search-wrapper-os + 1.1.6 + + + UTF-8 + 21 + 21 + 2.17.0 + + + + + Mozilla Public License, Version 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + repo + + + + + + com.greendelta.search + search-wrapper-api + ${project.version} + + + org.opensearch + opensearch + ${opensearch.version} + + + org.opensearch.client + opensearch-rest-high-level-client + ${opensearch.version} + + + + \ No newline at end of file diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Aggregation.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Aggregation.java new file mode 100644 index 0000000..11fe085 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Aggregation.java @@ -0,0 +1,62 @@ +package com.greendelta.search.wrapper.os; + +import org.opensearch.search.aggregations.AggregationBuilder; +import org.opensearch.search.aggregations.AggregationBuilders; + +import com.greendelta.search.wrapper.aggregations.RangeAggregation; +import com.greendelta.search.wrapper.aggregations.SearchAggregation; +import com.greendelta.search.wrapper.aggregations.TermsAggregation; + +class Aggregation { + + static AggregationBuilder builder(SearchAggregation aggregation) { + var builder = createBuilder(aggregation); + if (isNested(aggregation.field)) { + builder = nest(builder, aggregation); + } + return builder; + } + + private static AggregationBuilder createBuilder(SearchAggregation aggregation) { + return switch (aggregation.type) { + case TERM -> termsBuilder((TermsAggregation) aggregation); + case RANGE -> rangeBuilder((RangeAggregation) aggregation); + default -> null; + }; + } + + private static AggregationBuilder termsBuilder(TermsAggregation aggregation) { + return AggregationBuilders.terms(aggregation.name).field(aggregation.field).size(Integer.MAX_VALUE); + } + + private static AggregationBuilder rangeBuilder(RangeAggregation aggregation) { + var builder = AggregationBuilders.range(aggregation.name).field(aggregation.field); + for (var range : aggregation.ranges) { + if (range[0] == null) { + builder.addUnboundedTo(range[1]); + } else if (range[1] == null) { + builder.addUnboundedFrom(range[0]); + } else { + builder.addRange(range[0], range[1]); + } + } + return builder; + } + + private static AggregationBuilder nest(AggregationBuilder builder, SearchAggregation aggregation) { + var path = aggregation.field; + var name = aggregation.name; + builder.subAggregation(AggregationBuilders.reverseNested(name + "-r")); + while (path.contains(".")) { + name += "-n"; + path = path.substring(0, path.lastIndexOf(".")); + builder = AggregationBuilders.nested(name, path).subAggregation(builder); + } + return builder; + } + + private static boolean isNested(String field) { + return field.contains("."); + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsClient.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsClient.java new file mode 100644 index 0000000..58a969a --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsClient.java @@ -0,0 +1,243 @@ +package com.greendelta.search.wrapper.os; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.opensearch.action.DocWriteRequest.OpType; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; +import org.opensearch.action.bulk.BulkRequestBuilder; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.client.Client; +import org.opensearch.client.Requests; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.script.Script; +import org.opensearch.script.ScriptType; + +import com.greendelta.search.wrapper.SearchClient; +import com.greendelta.search.wrapper.SearchQuery; +import com.greendelta.search.wrapper.SearchResult; + +public class OsClient implements SearchClient { + + private final Client client; + private final String indexName; + + public OsClient(Client client, String indexName) { + this.client = client; + this.indexName = indexName; + } + + @Override + public SearchResult> search(SearchQuery searchQuery) { + try { + var request = new Request(client, indexName); + return Search.run(request, searchQuery); + } catch (Exception e) { + e.printStackTrace(); + return new SearchResult<>(); + } + } + + @Override + public Set searchIds(SearchQuery searchQuery) { + var request = new Request(client, indexName); + return Search.ids(request, searchQuery); + } + + @Override + public void create(Map settings) { + var exists = client.admin().indices().prepareExists(indexName).execute().actionGet().isExists(); + if (exists) + return; + var indexSettings = settings.get("config"); + var request = new CreateIndexRequest(indexName); + request.settings(Settings.builder() + .loadFromSource(indexSettings, XContentType.JSON).put("number_of_shards", 1)); + client.admin().indices() + .create(request).actionGet(); + var mapping = settings.get("mapping"); + var mappingRequest = Requests.putMappingRequest(indexName); + mappingRequest.source(mapping, XContentType.JSON); + client.admin().indices().putMapping(mappingRequest).actionGet(); + } + + @Override + public void index(String id, Map content) { + client.index(indexRequest(id, content, true)).actionGet(); + } + + @Override + public void index(Map> contentsById) { + var builder = client.prepareBulk(); + for (var id : contentsById.keySet()) { + var content = contentsById.get(id); + builder.add(indexRequest(id, content, false)); + } + client.bulk(builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE).request()).actionGet(); + } + + private IndexRequest indexRequest(String id, Map content, boolean refresh) { + var builder = client.prepareIndex(indexName).setId(id); + builder.setOpType(OpType.INDEX).setSource(content); + if (refresh) { + builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return builder.request(); + } + + @Override + public void update(String id, Map update) { + client.update(updateRequest(id, update, true)).actionGet(); + } + + @Override + public void update(String id, String script, Map parameters) { + client.update(updateRequest(id, script, parameters, true)).actionGet(); + } + + @Override + public void update(Set ids, Map update) { + var builder = client.prepareBulk(); + for (var id : ids) { + builder.add(updateRequest(id, update, false)); + } + client.bulk(builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE).request()).actionGet(); + } + + @Override + public void update(Set ids, String script, Map parameters) { + var builder = client.prepareBulk(); + for (var id : ids) { + builder.add(updateRequest(id, script, parameters, false)); + } + client.bulk(builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE).request()).actionGet(); + } + + @Override + public void update(Map> updatesById) { + BulkRequestBuilder builder = client.prepareBulk(); + for (var id : updatesById.keySet()) { + var update = updatesById.get(id); + builder.add(updateRequest(id, update, false)); + } + client.bulk(builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE).request()).actionGet(); + } + + private UpdateRequest updateRequest(String id, Map content, boolean refresh) { + var builder = client.prepareUpdate(indexName, id); + builder.setDoc(content); + if (refresh) { + builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return builder.request(); + } + + private UpdateRequest updateRequest(String id, String script, Map parameters, boolean refresh) { + var builder = client.prepareUpdate(indexName, id); + builder.setScript(new Script(ScriptType.INLINE, "painless", script, parameters)); + if (refresh) { + builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return builder.request(); + } + + @Override + public void remove(String id) { + client.delete(deleteRequest(id, true)).actionGet(); + } + + @Override + public void remove(Set ids) { + var bulk = client.prepareBulk(); + for (var id : ids) { + bulk.add(deleteRequest(id, false)); + } + client.bulk(bulk.setRefreshPolicy(RefreshPolicy.IMMEDIATE).request()).actionGet(); + } + + private DeleteRequest deleteRequest(String id, boolean refresh) { + var builder = client.prepareDelete(indexName, id); + if (refresh) { + builder.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return builder.request(); + } + + @Override + public boolean has(String id) { + var builder = client.prepareGet(indexName, id); + var response = client.get(builder.request()).actionGet(); + if (response == null) + return false; + return response.isExists(); + } + + @Override + public Map get(String id) { + var builder = client.prepareGet(indexName, id); + var response = client.get(builder.request()).actionGet(); + if (response == null) + return null; + var source = response.getSource(); + if (source == null || source.isEmpty()) + return null; + return source; + } + + @Override + public List> get(Set ids) { + var builder = client.prepareMultiGet(); + builder.add(indexName, ids); + var response = client.multiGet(builder.request()).actionGet(); + if (response == null) + return null; + var results = new ArrayList>(); + var it = response.iterator(); + while (it.hasNext()) { + var resp = it.next().getResponse(); + if (resp == null) + continue; + var source = resp.getSource(); + if (source == null || source.isEmpty()) + continue; + results.add(source); + } + return results; + } + + @Override + public void clear() { + var exists = client.admin().indices().prepareExists(indexName).execute().actionGet().isExists(); + if (!exists) + return; + var mapping = client.admin().indices() + .prepareGetMappings(indexName).execute().actionGet() + .getMappings().get(indexName).getSourceAsMap(); + client.admin().indices() + .delete(new DeleteIndexRequest(indexName)).actionGet(); + var request = new CreateIndexRequest(indexName); + request.settings(Settings.builder().put("max_result_window", 2147483647).put("number_of_shards", 1)); + client.admin().indices() + .create(request).actionGet(); + var mappingRequest = Requests.putMappingRequest(indexName); + mappingRequest.source(mapping); + client.admin().indices() + .putMapping(mappingRequest).actionGet(); + } + + @Override + public void delete() { + var exists = client.admin().indices().prepareExists(indexName).execute().actionGet().isExists(); + if (!exists) + return; + client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet(); + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsRestClient.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsRestClient.java new file mode 100644 index 0000000..4ac0e6b --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/OsRestClient.java @@ -0,0 +1,281 @@ +package com.greendelta.search.wrapper.os; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.opensearch.action.DocWriteRequest.OpType; +import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; +import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.client.indices.CreateIndexRequest; +import org.opensearch.client.indices.GetIndexRequest; +import org.opensearch.client.indices.GetMappingsRequest; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.script.Script; +import org.opensearch.script.ScriptType; + +import com.greendelta.search.wrapper.SearchClient; +import com.greendelta.search.wrapper.SearchQuery; +import com.greendelta.search.wrapper.SearchResult; + +public class OsRestClient implements SearchClient { + + private final RestHighLevelClient client; + private final String indexName; + + public OsRestClient(RestHighLevelClient client, String indexName) { + this.client = client; + this.indexName = indexName; + } + + @Override + public SearchResult> search(SearchQuery searchQuery) { + try { + var request = new RestRequest(client, indexName); + return Search.run(request, searchQuery); + } catch (Exception e) { + e.printStackTrace(); + return new SearchResult<>(); + } + } + + @Override + public Set searchIds(SearchQuery searchQuery) { + var request = new RestRequest(client, indexName); + return Search.ids(request, searchQuery); + } + + @Override + public void create(Map settings) { + try { + var exists = client.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); + if (exists) + return; + var config = settings.get("config"); + var mapping = settings.get("mapping"); + CreateIndexRequest request = new CreateIndexRequest(indexName) + .settings(Settings.builder().loadFromSource(config, XContentType.JSON)) + .mapping(mapping, XContentType.JSON); + client.indices().create(request, RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void index(String id, Map content) { + try { + client.index(indexRequest(id, content, true), RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void index(Map> contentsById) { + bulk(request -> contentsById.keySet() + .forEach(id -> request.add(indexRequest(id, contentsById.get(id), false)))); + } + + private IndexRequest indexRequest(String id, Map content, boolean refresh) { + var request = new IndexRequest(indexName).id(id).opType(OpType.INDEX).source(content); + if (refresh) { + request.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return request; + } + + @Override + public void update(String id, Map update) { + try { + client.update(updateRequest(id, update, true), RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void update(String id, String script, Map parameters) { + try { + client.update(updateRequest(id, script, parameters, true), RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void update(Set ids, Map update) { + bulk(request -> ids.forEach(id -> request.add(updateRequest(id, update, false)))); + + } + + @Override + public void update(Set ids, String script, Map parameters) { + bulk(request -> ids.forEach(id -> request.add(updateRequest(id, script, parameters, false)))); + } + + @Override + public void update(Map> updatesById) { + bulk(request -> updatesById.keySet().forEach(id -> request.add(updateRequest(id, updatesById.get(id), false)))); + + } + + private UpdateRequest updateRequest(String id, Map content, boolean refresh) { + var request = new UpdateRequest(indexName, id).doc(content); + if (refresh) { + request.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return request; + } + + private UpdateRequest updateRequest(String id, String script, Map parameters, boolean refresh) { + var request = new UpdateRequest(indexName, id) + .script(new Script(ScriptType.INLINE, "painless", script, parameters)); + if (refresh) { + request.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return request; + } + + @Override + public void remove(String id) { + try { + client.delete(deleteRequest(id, true), RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void remove(Set ids) { + bulk(request -> ids.forEach(id -> request.add(deleteRequest(id, false)))); + } + + private void bulk(Consumer createRequests) { + var request = new BulkRequest(); + createRequests.accept(request); + request.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + try { + client.bulk(request, RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + private DeleteRequest deleteRequest(String id, boolean refresh) { + var request = new DeleteRequest(indexName, id); + if (refresh) { + request.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + } + return request; + } + + @Override + public boolean has(String id) { + var request = new GetRequest(indexName, id); + try { + var response = client.get(request, RequestOptions.DEFAULT); + if (response == null) + return false; + return response.isExists(); + } catch (IOException e) { + // TODO handle exception + return false; + } + } + + @Override + public Map get(String id) { + var request = new GetRequest(indexName, id); + try { + var response = client.get(request, RequestOptions.DEFAULT); + if (response == null) + return null; + var source = response.getSource(); + if (source == null || source.isEmpty()) + return null; + return source; + } catch (IOException e) { + // TODO handle exception + return null; + } + } + + @Override + public List> get(Set ids) { + var request = new MultiGetRequest(); + ids.forEach(id -> request.add(indexName, id)); + try { + var response = client.mget(request, RequestOptions.DEFAULT); + if (response == null) + return null; + var results = new ArrayList>(); + var it = response.iterator(); + while (it.hasNext()) { + var resp = it.next().getResponse(); + if (resp == null) + continue; + var source = resp.getSource(); + if (source == null || source.isEmpty()) + continue; + results.add(source); + } + return results; + } catch (IOException e) { + // TODO handle exception + return null; + } + } + + @Override + public void clear() { + try { + boolean exists = client.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); + if (!exists) + return; + var settings = client.indices() + .getSettings(new GetSettingsRequest().indices(indexName), RequestOptions.DEFAULT) + .getIndexToSettings().get(indexName); + settings = settings.filter(key -> switch (key) { + case "index.provided_name", "index.creation_date", "index.uuid", "index.version.created" -> false; + default -> true; + }); + var mapping = client.indices() + .getMapping(new GetMappingsRequest().indices(indexName), RequestOptions.DEFAULT).mappings() + .get(indexName).getSourceAsMap(); + client.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT); + var request = new CreateIndexRequest(indexName) + .settings(settings) + .mapping(mapping); + client.indices().create(request, RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + + @Override + public void delete() { + try { + var exists = client.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); + if (!exists) + return; + client.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT); + } catch (IOException e) { + // TODO handle exception + } + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Query.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Query.java new file mode 100644 index 0000000..435918b --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Query.java @@ -0,0 +1,183 @@ +package com.greendelta.search.wrapper.os; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.lucene.search.join.ScoreMode; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; +import org.opensearch.index.query.functionscore.ScoreFunctionBuilders; + +import com.greendelta.search.wrapper.Conjunction; +import com.greendelta.search.wrapper.MultiSearchFilter; +import com.greendelta.search.wrapper.SearchFilterValue; +import com.greendelta.search.wrapper.SearchQuery; + +class Query { + + static QueryBuilder create(SearchQuery searchQuery) { + var bool = QueryBuilders.boolQuery(); + searchQuery.getFilters().forEach(filter -> { + QueryBuilder query = create(filter.field, filter.conjunction, filter.values); + append(bool, query, Conjunction.AND); + }); + searchQuery.getMultiFilters().forEach(filter -> { + QueryBuilder query = query(filter); + append(bool, query, Conjunction.AND); + }); + var query = simplify(bool); + if (query == null) { + query = QueryBuilders.matchAllQuery(); + } + return score(query, searchQuery); + } + + private static QueryBuilder create(String field, Conjunction conjunction, Set values) { + if (values.isEmpty()) + return null; + var bool = QueryBuilders.boolQuery(); + values.forEach(value -> { + var query = create(field, value); + append(bool, query, conjunction); + }); + return simplify(bool); + } + + private static QueryBuilder query(MultiSearchFilter filter) { + if (filter.values.isEmpty()) + return null; + var bool = QueryBuilders.boolQuery(); + filter.fields.forEach(field -> { + var query = create(field, filter.conjunction, filter.values); + append(bool, query, Conjunction.OR); + }); + return simplify(bool); + } + + private static void append(BoolQueryBuilder boolQuery, QueryBuilder query, Conjunction conjunction) { + if (query == null) + return; + if (conjunction == Conjunction.AND) { + boolQuery.must(query); + } else if (conjunction == Conjunction.OR) { + boolQuery.should(query); + } + } + + private static QueryBuilder simplify(BoolQueryBuilder query) { + var queries = query.must().size() + query.should().size(); + if (queries == 0) + return null; + if (queries == 1 && query.must().isEmpty()) + return query.should().get(0); + if (queries == 1 && query.should().isEmpty()) + return query.must().get(0); + return query; + } + + private static QueryBuilder create(String field, SearchFilterValue value) { + var builder = builder(field, value); + if (builder == null) + return null; + return decorate(builder, field, value); + } + + private static QueryBuilder builder(String field, SearchFilterValue value) { + if (value.value == null || value.value.toString().isEmpty()) + return null; + return switch (value.type) { + case TERM -> terms(field, value); + case PHRASE -> phrase(field, value); + case WILDCARD -> wildcard(field, value); + case RANGE -> range(field, value); + default -> null; + }; + } + + private static QueryBuilder terms(String field, SearchFilterValue value) { + var terms = toCollection(value.value); + if (terms.size() == 1) + return QueryBuilders.termQuery(field, terms.get(0)); + return QueryBuilders.termsQuery(field, terms); + } + + private static QueryBuilder phrase(String field, SearchFilterValue value) { + var phrases = toCollection(value.value); + if (phrases.size() == 1) + return QueryBuilders.matchPhraseQuery(field, phrases.get(0)); + var query = QueryBuilders.boolQuery(); + phrases.forEach(phrase -> { + query.should(QueryBuilders.matchPhraseQuery(field, phrase)); + }); + return query; + } + + private static QueryBuilder wildcard(String field, SearchFilterValue value) { + return QueryBuilders.wildcardQuery(field, value.value.toString()); + } + + private static QueryBuilder range(String field, SearchFilterValue value) { + var v = (Object[]) value.value; + return QueryBuilders.rangeQuery(field).from(v[0]).to(v[1]); + } + + private static List toCollection(Object value) { + if (value == null) + return null; + if (value instanceof String && value.toString().isEmpty()) + return null; + if (value instanceof Collection) + return filterEmpty((Collection) value); + return Collections.singletonList(value); + } + + private static List filterEmpty(Collection values) { + return values.stream() + .filter(value -> value != null && !value.toString().isEmpty()) + .collect(Collectors.toList()); + } + + private static QueryBuilder decorate(QueryBuilder query, String field, SearchFilterValue value) { + if (query == null) + return null; + if (value.boost != null) { + query = query.boost(value.boost); + } + if (field.contains(".")) { + query = nest(query, field); + } + return query; + } + + private static QueryBuilder nest(QueryBuilder query, String field) { + var path = field; + while (path.contains(".")) { + path = path.substring(0, path.lastIndexOf(".")); + query = QueryBuilders.nestedQuery(path, query, ScoreMode.Total); + } + return query; + } + + private static QueryBuilder score(QueryBuilder query, SearchQuery searchQuery) { + if (searchQuery.getScores().isEmpty()) + return query; + var functions = new ArrayList(); + searchQuery.getScores().forEach(score -> { + var script = ScoreFunctionBuilders.scriptFunction(Script.from(score)); + functions.add(new FilterFunctionBuilder(script)); + }); + searchQuery.getFunctions().forEach(function -> { + var script = ScoreFunctionBuilders.linearDecayFunction(function.fieldName, + function.origin, function.scale, function.offset, function.decay); + functions.add(new FilterFunctionBuilder(script)); + }); + return QueryBuilders.functionScoreQuery(query, functions.toArray(new FilterFunctionBuilder[functions.size()])); + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Request.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Request.java new file mode 100644 index 0000000..bab013d --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Request.java @@ -0,0 +1,56 @@ +package com.greendelta.search.wrapper.os; + +import java.io.IOException; + +import org.opensearch.action.search.SearchRequestBuilder; +import org.opensearch.client.Client; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.search.aggregations.AggregationBuilder; +import org.opensearch.search.sort.SortOrder; + +import com.greendelta.search.wrapper.os.Search.OsRequest; + +class Request implements OsRequest { + + private final SearchRequestBuilder request; + + Request(Client client, String indexName) { + request = client.prepareSearch(indexName); + } + + @Override + public void setFrom(int from) { + request.setFrom(from); + } + + @Override + public void setSize(int size) { + request.setSize(size); + } + + @Override + public void addSort(String field, SortOrder order) { + request.addSort(field, order); + } + + @Override + public void addAggregation(AggregationBuilder aggregation) { + request.addAggregation(aggregation); + } + + @Override + public void setQuery(QueryBuilder query) { + request.setQuery(query); + } + + @Override + public void addField(String field) { + request.addFetchField(field); + } + + @Override + public Response execute() throws IOException { + return new Response(request.execute().actionGet()); + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Response.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Response.java new file mode 100644 index 0000000..fd60c5a --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Response.java @@ -0,0 +1,57 @@ +package com.greendelta.search.wrapper.os; + +import java.util.ArrayList; +import java.util.List; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.bucket.range.InternalRange; +import org.opensearch.search.aggregations.bucket.terms.DoubleTerms; +import org.opensearch.search.aggregations.bucket.terms.LongTerms; +import org.opensearch.search.aggregations.bucket.terms.StringTerms; +import org.opensearch.search.aggregations.bucket.terms.Terms.Bucket; + +import com.greendelta.search.wrapper.os.Search.OsResponse; + +class Response implements OsResponse { + + private final SearchResponse response; + + Response(SearchResponse response) { + this.response = response; + } + + @Override + public SearchHit[] getHits() { + return response.getHits().getHits(); + } + + @Override + public long getTotalHits() { + return response.getHits().getTotalHits().value; + } + + @Override + public List getAggregations() { + if (response.getAggregations() == null) + return new ArrayList<>(); + return response.getAggregations().asList(); + } + + @Override + public List getTermBuckets(Aggregation aggregation) { + return switch (aggregation.getType()) { + case StringTerms.NAME -> ((StringTerms) aggregation).getBuckets(); + case LongTerms.NAME -> ((LongTerms) aggregation).getBuckets(); + case DoubleTerms.NAME -> ((DoubleTerms) aggregation).getBuckets(); + default -> new ArrayList<>(); + }; + } + + public List getRangeBuckets( + Aggregation aggregation) { + return ((InternalRange) aggregation).getBuckets(); + } + +} \ No newline at end of file diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestRequest.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestRequest.java new file mode 100644 index 0000000..c4c9ac5 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestRequest.java @@ -0,0 +1,60 @@ +package com.greendelta.search.wrapper.os; + +import java.io.IOException; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.search.aggregations.AggregationBuilder; +import org.opensearch.search.sort.SortOrder; + +import com.greendelta.search.wrapper.os.Search.OsRequest; + +class RestRequest implements OsRequest { + + private final RestHighLevelClient client; + private final SearchRequest request; + + RestRequest(RestHighLevelClient client, String indexName) { + this.client = client; + this.request = new SearchRequest(indexName); + } + + @Override + public void setFrom(int from) { + request.source().from(from); + } + + @Override + public void setSize(int size) { + request.source().size(size); + } + + @Override + public void addSort(String field, SortOrder order) { + request.source().sort(field, order); + } + + @Override + public void addAggregation(AggregationBuilder aggregation) { + request.source().aggregation(aggregation); + } + + @Override + public void setQuery(QueryBuilder query) { + request.source().query(query); + request.source().trackTotalHits(true); + } + + @Override + public void addField(String field) { + request.source().fetchField(field); + } + + @Override + public RestResponse execute() throws IOException { + return new RestResponse(client.search(request, RequestOptions.DEFAULT)); + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestResponse.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestResponse.java new file mode 100644 index 0000000..11eb959 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/RestResponse.java @@ -0,0 +1,60 @@ +package com.greendelta.search.wrapper.os; + +import java.util.ArrayList; +import java.util.List; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.search.SearchHit; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.bucket.range.ParsedRange; +import org.opensearch.search.aggregations.bucket.terms.DoubleTerms; +import org.opensearch.search.aggregations.bucket.terms.LongTerms; +import org.opensearch.search.aggregations.bucket.terms.ParsedDoubleTerms; +import org.opensearch.search.aggregations.bucket.terms.ParsedLongTerms; +import org.opensearch.search.aggregations.bucket.terms.ParsedStringTerms; +import org.opensearch.search.aggregations.bucket.terms.StringTerms; +import org.opensearch.search.aggregations.bucket.terms.Terms.Bucket; + +import com.greendelta.search.wrapper.os.Search.OsResponse; + +class RestResponse implements OsResponse { + + private final SearchResponse response; + + RestResponse(SearchResponse response) { + this.response = response; + } + + @Override + public SearchHit[] getHits() { + return response.getHits().getHits(); + } + + @Override + public long getTotalHits() { + return response.getHits().getTotalHits().value; + } + + @Override + public List getAggregations() { + if (response.getAggregations() == null) + return new ArrayList<>(); + return response.getAggregations().asList(); + } + + @Override + public List getTermBuckets(Aggregation aggregation) { + return switch (aggregation.getType()) { + case StringTerms.NAME -> ((ParsedStringTerms) aggregation).getBuckets(); + case LongTerms.NAME -> ((ParsedLongTerms) aggregation).getBuckets(); + case DoubleTerms.NAME -> ((ParsedDoubleTerms) aggregation).getBuckets(); + default -> new ArrayList<>(); + }; + } + + public List getRangeBuckets( + Aggregation aggregation) { + return ((ParsedRange) aggregation).getBuckets(); + } + +} \ No newline at end of file diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Result.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Result.java new file mode 100644 index 0000000..fbf5a56 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Result.java @@ -0,0 +1,106 @@ +package com.greendelta.search.wrapper.os; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.bucket.nested.ParsedNested; +import org.opensearch.search.aggregations.bucket.nested.ParsedReverseNested; +import org.opensearch.search.aggregations.bucket.terms.DoubleTerms; +import org.opensearch.search.aggregations.bucket.terms.LongTerms; +import org.opensearch.search.aggregations.bucket.terms.StringTerms; + +import com.greendelta.search.wrapper.SearchFilterType; +import com.greendelta.search.wrapper.SearchQuery; +import com.greendelta.search.wrapper.SearchResult; +import com.greendelta.search.wrapper.aggregations.RangeAggregation; +import com.greendelta.search.wrapper.aggregations.TermsAggregation; +import com.greendelta.search.wrapper.aggregations.results.AggregationResult; +import com.greendelta.search.wrapper.aggregations.results.AggregationResultBuilder; +import com.greendelta.search.wrapper.aggregations.results.AggregationResultEntry; +import com.greendelta.search.wrapper.os.Search.OsResponse; + +class Result { + + private static final String RANGE_TYPE = "range"; + private static final String NESTED_TYPE = "nested"; + + static List aggregations(OsResponse response) { + return addAggregations(response, response.getAggregations()); + } + + static List addAggregations(OsResponse response, List aggreagtions) { + var results = new ArrayList(); + for (var aggregation : aggreagtions) { + if (!aggregation.getType().equals(NESTED_TYPE)) { + results.add(addAggregation(response, aggregation)); + } else { + results.addAll(addAggregations(response, ((ParsedNested) aggregation).getAggregations().asList())); + } + } + return results; + } + + private static AggregationResult addAggregation(OsResponse response, Aggregation aggregation) { + var builder = new AggregationResultBuilder(); + builder.name(aggregation.getName()).type(mapType(aggregation.getType())); + putEntries(response, aggregation, builder); + return builder.build(); + } + + private static SearchFilterType mapType(String type) { + if (type == null) + return null; + return switch (type) { + case StringTerms.NAME, LongTerms.NAME, DoubleTerms.NAME -> TermsAggregation.TYPE; + case RANGE_TYPE -> RangeAggregation.TYPE; + default -> SearchFilterType.UNKNOWN; + }; + } + + private static void putEntries(OsResponse response, Aggregation aggregation, AggregationResultBuilder builder) { + var totalCount = 0; + switch (aggregation.getType()) { + case StringTerms.NAME, LongTerms.NAME, DoubleTerms.NAME: + for (var bucket : response.getTermBuckets(aggregation)) { + var count = getCount(bucket.getDocCount(), bucket.getAggregations().asList()); + builder.addEntry(new AggregationResultEntry(bucket.getKeyAsString(), count)); + totalCount += count; + } + builder.totalCount(totalCount); + break; + case RANGE_TYPE: + for (var bucket : response.getRangeBuckets(aggregation)) { + var data = new Object[] { bucket.getFrom(), bucket.getTo() }; + var count = getCount(bucket.getDocCount(), bucket.getAggregations().asList()); + builder.addEntry(new AggregationResultEntry(bucket.getKeyAsString(), count, data)); + totalCount += count; + } + builder.totalCount(totalCount); + break; + } + } + + private static long getCount(long bucketCount, List aggregations) { + for (var aggregation : aggregations) + if (aggregation instanceof ParsedReverseNested nested) + return nested.getDocCount(); + return bucketCount; + } + + static void extend(SearchResult> result, long totalHits, SearchQuery searchQuery) { + result.resultInfo.totalCount = totalHits; + result.resultInfo.currentPage = searchQuery.getPage(); + result.resultInfo.pageSize = searchQuery.getPageSize(); + var totalCount = result.resultInfo.totalCount; + if (searchQuery.getPageSize() != 0) { + var pageCount = (int) totalCount / searchQuery.getPageSize(); + if ((totalCount % searchQuery.getPageSize()) != 0) { + pageCount = 1 + pageCount; + } + result.resultInfo.pageCount = pageCount; + } + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Script.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Script.java new file mode 100644 index 0000000..234bbb0 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Script.java @@ -0,0 +1,107 @@ +package com.greendelta.search.wrapper.os; + +import com.greendelta.search.wrapper.score.Case; +import com.greendelta.search.wrapper.score.Comparator; +import com.greendelta.search.wrapper.score.Score; + +class Script { + + static String from(Score score) { + var dWeight = score.getDefaultWeight(); + if (score.getCases().length == 0) + return "return " + dWeight + ";"; + var s = getMethods(score); + s += "def[] fieldValues = new def[" + score.fields.size() + "];"; + s += "def[] values = new def[" + score.fields.size() + "];"; + for (var i = 0; i < score.fields.size(); i++) { + var field = score.fields.get(i); + var escaper = field.value instanceof String ? "\"" : ""; + var numDef = field.value instanceof Long ? "L" : ""; + s += "fieldValues[" + i + "] = doc['" + field.name + "'].getValue();"; + if (field.lowerLimit != null) { + s += "if (fieldValues[" + i + "] < " + field.lowerLimit + ") { return " + dWeight + "; }"; + } + if (field.upperLimit != null) { + s += "if (fieldValues[" + i + "] > " + field.upperLimit + ") { return " + dWeight + "; }"; + } + s += "values[" + i + "] = " + escaper + "" + field.value + "" + escaper + numDef + ";"; + } + s += cases(score); + return s; + } + + private static String cases(Score score) { + var s = ""; + var hadElse = false; + for (var c : score.getCases()) { + if (!c.conditions.isEmpty()) { + s += conditions(c); + } else { + s += "return " + c.weight + ";"; + hadElse = true; + break; + } + } + if (!hadElse) { + s += "return " + score.getDefaultWeight() + ";"; // default case + } + return s; + } + + private static String conditions(Case scoreCase) { + var s = "if ("; + var firstCondition = true; + for (var con : scoreCase.conditions) { + if (!firstCondition) { + s += " && "; + } + if (con.comparator == Comparator.EQUALS) { + s += "(" + con.value1 + ") != null && " + (con.value1) + ".equals(" + con.value2 + ")"; + } else { + var numDef1 = con.value1 instanceof Long ? "L" : ""; + var numDef2 = con.value2 instanceof Long ? "L" : ""; + s += con.value1 + numDef1 + " " + toString(con.comparator) + " " + con.value2 + numDef2; + } + firstCondition = false; + } + s += ") { return " + scoreCase.weight + "; } "; + return s; + } + + private static String getDistanceMethod() { + var s = "double toRad(double degree) { return degree * Math.PI / 180; }"; + s += "double getDistance(double lat1, double lon1, double lat2, double lon2) { "; + s += "double earthRadius = 6371;"; + s += "double rdLat = toRad(lat2-lat1);"; + s += "double rdLon = toRad(lon2-lon1);"; + s += "double rLat1 = toRad(lat1);"; + s += "double rLat2 = toRad(lat2);"; + s += "double a = Math.sin(rdLat/2) * Math.sin(rdLat/2) + Math.sin(rdLon/2) * Math.sin(rdLon/2) * Math.cos(rLat1) * Math.cos(rLat2);"; + s += "double b = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));"; + s += "return earthRadius * b;"; + s += "}"; + return s; + } + + private static String getMethods(Score score) { + var s = getDistanceMethod(); + s += "String substring(String value, int from, int to) { if (value == null || from == -1 || to == -1) { return null; } return value.substring(from, to); }"; + s += "int indexOf(String value, String phrase) { if (value == null || phrase == null) { return -1; } return value.indexOf(phrase); }"; + s += "int lastIndexOf(String value, String phrase) { if (value == null || phrase == null) { return -1; } return value.lastIndexOf(phrase); }"; + s += "double abs(double value) { return Math.abs(value); }"; + s += "double min(double v1, double v2) { return Math.min(v1, v2); }"; + return s; + } + + private static String toString(Comparator comparator) { + return switch (comparator) { + case IS -> "=="; + case IS_LESS_THAN -> "<"; + case IS_LESS_OR_EQUAL_THAN -> "<="; + case IS_GREATER_THAN -> ">"; + case IS_GREATER_OR_EQUAL_THAN -> ">="; + default -> "=="; + }; + } + +} diff --git a/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Search.java b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Search.java new file mode 100644 index 0000000..e4fc2e5 --- /dev/null +++ b/search-wrapper-os/src/main/java/com/greendelta/search/wrapper/os/Search.java @@ -0,0 +1,201 @@ +package com.greendelta.search.wrapper.os; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.search.SearchHit; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.AggregationBuilder; +import org.opensearch.search.aggregations.bucket.terms.Terms.Bucket; +import org.opensearch.search.sort.SortOrder; + +import com.greendelta.search.wrapper.SearchQuery; +import com.greendelta.search.wrapper.SearchResult; +import com.greendelta.search.wrapper.SearchSorting; + +class Search { + + private static final Logger log = LogManager.getLogger(Search.class); + + static SearchResult> run(OsRequest request, SearchQuery searchQuery) { + prepare(request, searchQuery); + try { + var result = new SearchResult>(); + OsResponse response = null; + var doContinue = true; + var totalHits = 0l; + while (doContinue) { + if (!searchQuery.isPaged()) { + request.setFrom(result.data.size()); + } + response = request.execute(); + for (var hit : response.getHits()) { + if (searchQuery.getFullResult()) { + result.data.add(hit.getSourceAsMap()); + } else if (searchQuery.getFields().isEmpty()) { + result.data.add(Collections.singletonMap("documentId", hit.getId())); + } else { + var fields = hit.getFields(); + var map = new HashMap(); + for (var field : searchQuery.getFields()) { + if (!fields.containsKey(field.name)) + continue; + putFieldValue(map, field.name, fields.get(field.name).getValues(), field.isArray); + } + result.data.add(map); + } + } + totalHits = response.getTotalHits(); + doContinue = !searchQuery.isPaged() && result.data.size() != totalHits; + result.aggregations.addAll(Result.aggregations(response)); + } + result.resultInfo.count = result.data.size(); + Result.extend(result, totalHits, searchQuery); + return result; + } catch (Exception e) { + if (searchQuery.getThrowErrors()) + throw new RuntimeException(e); + log.error("Error during search", e); + var result = new SearchResult>(); + Result.extend(result, 0, searchQuery); + return result; + } + } + + @SuppressWarnings("unchecked") + private static void putFieldValue(Map map, String field, List values, boolean array) { + if (!field.contains(".")) { + if (array) { + map.put(field, values); + } else { + map.put(field, !values.isEmpty() ? values.get(0) : null); + } + return; + } + var first = field.substring(0, field.indexOf(".")); + var rest = field.substring(field.indexOf(".") + 1); + if (array) { + var list = (List>) map.computeIfAbsent(first, + k -> values.stream().map(v -> new HashMap()).collect(Collectors.toList())); + for (var i = 0; i < values.size(); i++) { + list.get(i).put(rest, values.get(i)); + } + } else { + var subMap = (Map) map.computeIfAbsent(first, k -> new HashMap<>()); + putFieldValue(subMap, rest, values, false); + } + } + + static Set ids(OsRequest request, SearchQuery searchQuery) { + prepare(request, searchQuery); + try { + var ids = new HashSet(); + OsResponse response = null; + var doContinue = true; + var totalHits = 0l; + while (doContinue) { + if (!searchQuery.isPaged()) { + request.setFrom(ids.size()); + } + response = request.execute(); + for (var hit : response.getHits()) { + ids.add(hit.getId()); + } + totalHits = response.getTotalHits(); + doContinue = !searchQuery.isPaged() && ids.size() != totalHits; + } + return ids; + } catch (Exception e) { + // TODO handle exception + return new HashSet<>(); + } + + } + + private static OsRequest prepare(OsRequest request, SearchQuery searchQuery) { + setupPaging(request, searchQuery); + setupSorting(request, searchQuery); + setupAggregations(request, searchQuery); + request.setQuery(Query.create(searchQuery)); + if (!searchQuery.getFullResult()) { + for (var field : searchQuery.getFields()) { + request.addField(field.name); + } + } + return request; + } + + private static void setupPaging(OsRequest request, SearchQuery searchQuery) { + var start = (searchQuery.getPage() - 1) * searchQuery.getPageSize(); + if (start > 0) { + request.setFrom(start); + } + if (!searchQuery.isPaged()) { + request.setSize(10000); + } else { + if (searchQuery.getPageSize() > 0) { + request.setSize(searchQuery.getPageSize()); + } else { + request.setSize(SearchQuery.DEFAULT_PAGE_SIZE); + } + } + } + + private static void setupSorting(OsRequest request, SearchQuery searchQuery) { + for (var entry : searchQuery.getSortBy().entrySet()) { + var value = entry.getValue() == SearchSorting.ASC + ? SortOrder.ASC + : SortOrder.DESC; + request.addSort(entry.getKey(), value); + } + } + + private static void setupAggregations(OsRequest request, SearchQuery searchQuery) { + for (var aggregation : searchQuery.getAggregations()) { + request.addAggregation(com.greendelta.search.wrapper.os.Aggregation.builder(aggregation)); + } + } + + interface OsRequest { + + void setFrom(int from); + + void setSize(int size); + + void addSort(String field, SortOrder order); + + void addAggregation(AggregationBuilder aggregation); + + void setQuery(QueryBuilder query); + + void addField(String field); + + OsResponse execute() throws IOException; + + } + + interface OsResponse { + + SearchHit[] getHits(); + + long getTotalHits(); + + List getAggregations(); + + List getTermBuckets(Aggregation aggregation); + + List getRangeBuckets( + Aggregation aggregation); + + } + +}