├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── pom.xml └── src ├── main ├── assemblies │ └── plugin.xml ├── java │ └── org │ │ └── elasticsearch │ │ ├── plugin │ │ └── termlist │ │ │ └── FacetPlugin.java │ │ └── search │ │ └── facet │ │ └── termlist │ │ ├── InternalTermListFacet.java │ │ ├── TermListFacet.java │ │ ├── TermListFacetBuilder.java │ │ ├── TermListFacetExecutor.java │ │ └── TermListFacetParser.java └── resources │ └── es-plugin.properties └── test └── java └── org └── elasticsearch └── search └── facet └── termlist └── TermListFacetTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | /work 3 | /logs 4 | /.idea 5 | /target 6 | .DS_Store 7 | *.iml 8 | /.settings 9 | /.project 10 | /.classpath 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | elasticsearch-term-plugin 2 | 3 | Copyright 2009-2013 Shay Banon and ElasticSearch (http://www.elasticsearch.org) 4 | Copyright 2013 Endgame, Inc. (http://www.endgame.com/) 5 | 6 | This product includes software plugin developed for 7 | ElasticSearch and Shay Banon – (http://http://www.elasticsearch.org/) 8 | 9 | Inspiration was taken from Andrew Clegg and his ElasticSearch Approx Plugin 10 | https://github.com/ptdavteam/elasticsearch-approx-plugin 11 | 12 | Licensed under Apache License, Version 2.0 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Term List Matching Plugin for ElasticSearch 2 | ================================== 3 | 4 | Similar functionality to Lucene/Solr's TermsComponent. 5 | This Term List plugin will do simple matching against the complete term list found directly in Lucene. 6 | 7 | 1. Build this plugin: 8 | 9 | mvn clean compile test package 10 | # this will create a file here: target/releases/elasticsearch-term-plugin-1.0-SNAPSHOT.zip 11 | PLUGIN_PATH=`pwd`/target/releases/elasticsearch-term-plugin-1.0-SNAPSHOT.zip 12 | 13 | 2. Install the PLUGIN 14 | 15 | cd $ELASTICSEARCH_HOME 16 | ./bin/plugin -url file:/$PLUGIN_PATH -install elasticsearch-term-plugin 17 | 18 | 3. Updating the plugin 19 | 20 | cd $ELASTICSEARCH_HOME 21 | ./bin/plugin -remove elasticsearch-term-plugin 22 | ./bin/plugin -url file:/$PLUGIN_PATH -install elasticsearch-term-plugin 23 | 24 | 25 | Usage 26 | ========== 27 | 28 | ##### Version 29 | 30 | ElasticSearch version 0.90.6 31 | 32 | ##### Facet Parameters 33 | * fields - list of fields to examine for terms, this is the only required field 34 | * search - substring to search for (case insensitive) 35 | * max_per_shard - max number of terms to pull from a shard 36 | * prefix - (true/false) defaults to false, is the search to be used for prefix/starts with matching only? 37 | * sort - (true/false) default to true, should returned list by alpha sorted 38 | * case_insenstive - (true/false) defaults to true, should matching be done disregarding case 39 | 40 | ##### Facet example 41 | 42 | "facets" : { 43 | "term_list_facet" : { 44 | "term_list" : { 45 | "fields" : [ "company", "other.company" ], 46 | "search" : "alt", 47 | "prefix" : false, 48 | "sort" : true, 49 | "case_insenstive" : true, 50 | "max_per_shard" : 10 51 | } 52 | } 53 | } 54 | 55 | ##### Results Example 56 | ``` 57 | ... 58 | "facets":{ 59 | "term_list_facet":{ 60 | "_type":"term_list", 61 | "entries":[ 62 | "alternet magic inc.", 63 | "altech tech technology chickens", 64 | "system altibox corp", 65 | "baltiware tv station llc" 66 | ] 67 | } 68 | } 69 | ``` 70 | ###### setup test index 71 | Note the analyzer configuration of tokenizer keyword, if you for example use the whitespace tokenizer your results will be strange 72 | 73 | curl -XDELETE localhost:9200/my_test_index 74 | 75 | curl -XPOST localhost:9200/my_test_index -d '{ 76 | "settings" : { 77 | "analysis": { 78 | "analyzer": { 79 | "default": { 80 | "filter": "lowercase", 81 | "type": "custom", 82 | "tokenizer": "keyword" 83 | } 84 | } 85 | } 86 | } 87 | }' 88 | 89 | ###### place test data in index 90 | 91 | curl -XPUT localhost:9200/my_test_index/my_test_type/1 -d '{ 92 | "my_test_type" : { 93 | "company" : "Walt Disney", 94 | "desc" : "A movie company and a TV channel", 95 | "other" : {"company" : "Walt Disney Theme Parks"} 96 | } 97 | }' 98 | 99 | curl -XPUT localhost:9200/my_test_index/my_test_type/2 -d '{ 100 | "my_test_type" : { 101 | "company" : "Walts Plumbing", 102 | "desc" : "Best plumber in town", 103 | "other" : {"company" : "Walts waterproof rubber chicken company"} 104 | } 105 | }' 106 | 107 | ###### query data with custom facet 108 | 109 | curl -XGET localhost:9200/my_test_index/_search?pretty=1 -d '{ 110 | "query" : { 111 | "match_all" : { } 112 | }, 113 | "facets" : { 114 | "term_list_facet" : { 115 | "term_list" : { 116 | "fields" : [ "company", "other.company" ], 117 | "search" : "alt", 118 | "prefix" : false, 119 | "max_per_shard" : 10 120 | } 121 | } 122 | } 123 | }' 124 | 125 | 126 | 127 | 128 | 129 | 130 | License 131 | ------- 132 | 133 | elasticsearch-term-plugin 134 | 135 | Copyright 2013 [Endgame, Inc.](http://www.endgame.com/) 136 | 137 | ![Endgame, Inc.](http://www.endgame.com/images/navlogo.png) 138 | 139 | This product includes software plugin developed for 140 | ElasticSearch and Shay Banon – [Elasticsearch](http://www.elasticsearch.org/) 141 | 142 | Inspiration was taken from Andrew Clegg and his ElasticSearch Approx Plugin 143 | https://github.com/ptdavteam/elasticsearch-approx-plugin 144 | 145 | Licensed under the Apache License, Version 2.0 (the "License"); you may 146 | not use this file except in compliance with the License. You may obtain 147 | a copy of the License at 148 | 149 | http://www.apache.org/licenses/LICENSE-2.0 150 | 151 | Unless required by applicable law or agreed to in writing, 152 | software distributed under the License is distributed on an 153 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 154 | KIND, either express or implied. See the License for the 155 | specific language governing permissions and limitations 156 | under the License. 157 | 158 | 159 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | org.elasticsearch 4 | elasticsearch-term-plugin 5 | jar 6 | 1.0.3-SNAPSHOT 7 | elasticsearch-term-plugin 8 | 9 | 0.90.6 10 | 11 | 12 | 13 | junit 14 | junit 15 | 3.8.1 16 | test 17 | 18 | 19 | org.elasticsearch 20 | elasticsearch 21 | ${elasticsearch.version} 22 | compile 23 | 24 | 25 | com.google.guava 26 | guava 27 | 14.0.1 28 | compile 29 | 30 | 31 | com.google.code.findbugs 32 | jsr305 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 2.3.2 43 | 44 | 1.7 45 | 1.7 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-source-plugin 51 | 2.1.2 52 | 53 | 54 | attach-sources 55 | 56 | jar 57 | 58 | 59 | 60 | 61 | 62 | maven-assembly-plugin 63 | 2.3 64 | 65 | false 66 | ${project.build.directory}/releases/ 67 | 68 | ${basedir}/src/main/assemblies/plugin.xml 69 | 70 | 71 | 72 | 73 | package 74 | 75 | single 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-surefire-plugin 83 | 2.12 84 | 85 | -XX:-UseSplitVerifier 86 | 87 | 88 | 89 | org.codehaus.mojo 90 | cobertura-maven-plugin 91 | 2.5.1 92 | 93 | 94 | xml 95 | 96 | 97 | 98 | 99 | cobertura 100 | 101 | cobertura 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | deployment 111 | Internal Releases 112 | http://ci-1.eng.endgames.local:8081/nexus/content/repositories/releases/ 113 | 114 | 115 | deployment 116 | Internal Releases 117 | http://ci-1.eng.endgames.local:8081/nexus/content/repositories/snapshots/ 118 | 119 | 120 | 121 | 122 | scm:git:git@git.endgames.local:engineering/elasticsearch-term-plugin.git 123 | scm:git:git@git.endgames.local:engineering/elasticsearch-term-plugin.git 124 | scm:git:git@git.endgames.local:engineering/elasticsearch-term-plugin.git 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/assemblies/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugin 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | / 11 | true 12 | true 13 | 14 | org.elasticsearch:elasticsearch 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/plugin/termlist/FacetPlugin.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.plugin.termlist; 17 | 18 | import org.elasticsearch.common.inject.Module; 19 | import org.elasticsearch.plugins.AbstractPlugin; 20 | import org.elasticsearch.search.facet.FacetModule; 21 | import org.elasticsearch.search.facet.termlist.InternalTermListFacet; 22 | import org.elasticsearch.search.facet.termlist.TermListFacetParser; 23 | 24 | 25 | 26 | /** 27 | * This class registers the facets themselves with ES, as well as the stream classes 28 | * which govern how a facet is deserialized. 29 | */ 30 | public class FacetPlugin extends AbstractPlugin { 31 | 32 | @Override 33 | public String name() { 34 | return "facet-term-list-plugin"; 35 | } 36 | 37 | @Override 38 | public String description() { 39 | return "an ES plugin with similar functionality to Lucene/Solr's TermsComponent"; 40 | } 41 | 42 | @Override 43 | public void processModule(final Module module) { 44 | 45 | if(module instanceof FacetModule) 46 | { 47 | ((FacetModule) module).addFacetProcessor(TermListFacetParser.class); // our processor goes here 48 | InternalTermListFacet.registerStream(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/search/facet/termlist/InternalTermListFacet.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.HashSet; 22 | import java.util.Iterator; 23 | import java.util.List; 24 | import java.util.Set; 25 | 26 | import org.elasticsearch.common.bytes.BytesReference; 27 | import org.elasticsearch.common.bytes.HashedBytesArray; 28 | import org.elasticsearch.common.collect.Lists; 29 | import org.elasticsearch.common.io.stream.StreamInput; 30 | import org.elasticsearch.common.io.stream.StreamOutput; 31 | import org.elasticsearch.common.xcontent.XContentBuilder; 32 | import org.elasticsearch.common.xcontent.XContentBuilderString; 33 | import org.elasticsearch.search.facet.Facet; 34 | import org.elasticsearch.search.facet.InternalFacet; 35 | import org.elasticsearch.search.facet.terms.TermsFacet.ComparatorType; 36 | import org.elasticsearch.search.facet.terms.TermsFacet.Entry; 37 | import org.elasticsearch.search.facet.terms.strings.InternalStringTermsFacet.TermEntry; 38 | 39 | /** 40 | * InternalTermListFacet 41 | * Only handle Strings 42 | * 43 | */ 44 | public class InternalTermListFacet extends InternalFacet implements TermListFacet { 45 | 46 | private final String type = "term_list"; 47 | private static final BytesReference STREAM_TYPE = new HashedBytesArray(TermListFacet.TYPE.getBytes()); 48 | private Object[] strings; 49 | private boolean sort; 50 | 51 | /** 52 | * Instantiates a new internal string term list facet. 53 | * 54 | * @param facetName the facet name 55 | * @param strings the strings 56 | */ 57 | public InternalTermListFacet(final String facetName, final Object[] strings, boolean sort) { 58 | super(facetName); 59 | this.strings = strings; 60 | this.sort = sort; 61 | } 62 | 63 | /** 64 | * Instantiates a new internal term list facet. 65 | */ 66 | private InternalTermListFacet() { 67 | } 68 | 69 | /** 70 | * Register streams. 71 | */ 72 | public static void registerStream() { 73 | Streams.registerStream(STREAM, STREAM_TYPE); 74 | } 75 | 76 | /** The stream. */ 77 | static Stream STREAM = new Stream() { 78 | @Override 79 | public Facet readFacet(StreamInput in) throws IOException { 80 | return readTermListFacet(in); 81 | } 82 | }; 83 | 84 | /** 85 | * Read term list facet. 86 | * 87 | * @param in 88 | * the input stream 89 | * @return the internal term list facet 90 | * @throws IOException 91 | * Signals that an I/O exception has occurred. 92 | */ 93 | public static InternalTermListFacet readTermListFacet(final StreamInput in) throws IOException { 94 | final InternalTermListFacet facet = new InternalTermListFacet(); 95 | facet.readFrom(in); 96 | return facet; 97 | } 98 | 99 | @Override 100 | public void writeTo(final StreamOutput out) throws IOException { 101 | int size = strings.length; 102 | super.writeTo(out); 103 | out.writeVInt(size); 104 | for (int i = 0; i < size; i++) { 105 | out.writeString((String) strings[i]); 106 | } 107 | } 108 | 109 | @Override 110 | public void readFrom(final StreamInput in) throws IOException { 111 | super.readFrom(in); 112 | final int size = in.readVInt(); 113 | strings = new Object[size]; 114 | for (int i = 0; i < size; i++) { 115 | strings[i] = in.readString(); 116 | } 117 | } 118 | 119 | /** 120 | * Output JSON fields 121 | */ 122 | static final class Fields { 123 | /** The Constant _TYPE. */ 124 | static final XContentBuilderString _TYPE = new XContentBuilderString("_type"); 125 | 126 | /** The Constant ENTRIES. */ 127 | static final XContentBuilderString ENTRIES = new XContentBuilderString("entries"); 128 | } 129 | 130 | @Override 131 | public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { 132 | builder.startObject(this.getName()); 133 | builder.field(Fields._TYPE, TermListFacet.TYPE); 134 | builder.array(Fields.ENTRIES, strings); 135 | builder.endObject(); 136 | return builder; 137 | } 138 | 139 | @Override 140 | public String getType() { 141 | return type; 142 | } 143 | 144 | @Override 145 | public BytesReference streamType() { 146 | return STREAM_TYPE; 147 | } 148 | 149 | @Override 150 | public Facet reduce(ReduceContext context) { 151 | return myReduce(this.getName(), context.facets()); 152 | } 153 | 154 | /** 155 | * Takes a list of facets and returns a new facet containing the merged data from all of them. 156 | * 157 | * @param name the facet name 158 | * @param facets the facets 159 | * @return the resulting reduced facet 160 | */ 161 | public Facet myReduce(final String name, final List facets) { 162 | final Set reducedStrings = new HashSet(); 163 | 164 | for(final Facet facet : facets) { 165 | final InternalTermListFacet itlf = (InternalTermListFacet) facet; 166 | for(final Object obj : itlf.strings) { 167 | reducedStrings.add(obj.toString()); 168 | } 169 | } 170 | 171 | String[] strArr = reducedStrings.toArray( new String[ reducedStrings.size() ] ); 172 | 173 | if(sort) 174 | Arrays.sort( strArr ); 175 | 176 | return new InternalTermListFacet(name, strArr, sort ); 177 | } 178 | 179 | @Override 180 | public Iterator iterator() { 181 | return entries().iterator(); 182 | } 183 | 184 | @Override 185 | public List entries() { 186 | return Arrays.asList(strings); 187 | } 188 | 189 | @Override 190 | public List getEntries() { 191 | return entries(); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/search/facet/termlist/TermListFacet.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import java.util.List; 19 | 20 | import org.elasticsearch.search.facet.Facet; 21 | 22 | /** 23 | * Defines the content and abilities of a facet class. 24 | * 25 | */ 26 | public interface TermListFacet extends Facet, Iterable { 27 | 28 | /** 29 | * The type of the facet. 30 | */ 31 | public static final String TYPE = "term_list"; 32 | 33 | /** 34 | * An ordered list of term list facet entries. 35 | */ 36 | List entries(); 37 | 38 | /** 39 | * An ordered list of term list facet entries. 40 | */ 41 | List getEntries(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/search/facet/termlist/TermListFacetBuilder.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import java.io.IOException; 19 | import java.util.List; 20 | 21 | import org.elasticsearch.common.xcontent.XContentBuilder; 22 | import org.elasticsearch.search.builder.SearchSourceBuilderException; 23 | import org.elasticsearch.search.facet.FacetBuilder; 24 | 25 | /** 26 | * The Class TermListFacetBuilder. 27 | */ 28 | public class TermListFacetBuilder extends FacetBuilder { 29 | 30 | private List fields; 31 | private String search; 32 | private int maxPerShard; 33 | private boolean prefix = false; 34 | private boolean caseInsensitive = true; 35 | private boolean sort = true; 36 | 37 | /** 38 | * Instantiates a new term list facet builder. 39 | * 40 | * @param name the facet name 41 | */ 42 | protected TermListFacetBuilder(final String name) { 43 | super(name); 44 | } 45 | 46 | /** 47 | * @param prefix 48 | * @return 49 | */ 50 | public TermListFacetBuilder prefix(final boolean prefix) { 51 | this.prefix = prefix; 52 | return this; 53 | } 54 | 55 | /** 56 | * @param sort 57 | * @return 58 | */ 59 | public TermListFacetBuilder sort(final boolean sort) { 60 | this.sort = sort; 61 | return this; 62 | } 63 | 64 | /** 65 | * @param caseInsensitive 66 | * @return 67 | */ 68 | public TermListFacetBuilder caseInsensitive(final boolean caseInsensitive) { 69 | this.caseInsensitive = caseInsensitive; 70 | return this; 71 | } 72 | 73 | /** 74 | * The field names to retrieve terms for the TermListFacet. 75 | * 76 | * @param keyField the key field 77 | * @return the term list facet builder 78 | */ 79 | public TermListFacetBuilder fields(List fields) { 80 | this.fields = fields; 81 | return this; 82 | } 83 | 84 | /** 85 | * The field name to subquery match for in terms list. 86 | * 87 | * @param keyField the key field 88 | * @return the term list facet builder 89 | */ 90 | public TermListFacetBuilder search(final String search) { 91 | this.search = search; 92 | return this; 93 | } 94 | 95 | /** 96 | * Max term results per shard. Defaults to 1000. 97 | * 98 | * @param maxPerShard the max number of results per shard 99 | * @return the term list facet builder 100 | */ 101 | public TermListFacetBuilder maxPerShard(final int maxPerShard) { 102 | this.maxPerShard = maxPerShard; 103 | return this; 104 | } 105 | 106 | /* (non-Javadoc) 107 | * @see org.elasticsearch.common.xcontent.ToXContent#toXContent(org.elasticsearch.common.xcontent.XContentBuilder, org.elasticsearch.common.xcontent.ToXContent.Params) 108 | */ 109 | @Override 110 | public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { 111 | 112 | if( fields == null || fields.isEmpty() ) { 113 | throw new SearchSourceBuilderException("field name must be set for term list facet [" + name + "]"); 114 | } 115 | 116 | builder.startObject(name); 117 | builder.startObject(TermListFacet.TYPE); 118 | 119 | if(fields != null) 120 | builder.field("fields", fields); 121 | 122 | if(search != null) 123 | builder.field("search", search); 124 | 125 | builder.field("prefix", prefix); 126 | builder.field("sort", sort); 127 | builder.field("case_insenstive", caseInsensitive); 128 | 129 | if(maxPerShard > 0) 130 | builder.field("max_per_shard", maxPerShard); 131 | else 132 | builder.field("max_per_shard", 1000); 133 | 134 | builder.endObject(); 135 | addFilterFacetAndGlobal(builder, params); 136 | 137 | builder.endObject(); 138 | 139 | return builder; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/search/facet/termlist/TermListFacetExecutor.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import java.io.IOException; 19 | import java.util.Collection; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | 23 | import org.apache.lucene.index.AtomicReaderContext; 24 | import org.apache.lucene.index.Fields; 25 | import org.apache.lucene.index.Terms; 26 | import org.apache.lucene.index.TermsEnum; 27 | import org.apache.lucene.util.BytesRef; 28 | import org.elasticsearch.common.logging.ESLogger; 29 | import org.elasticsearch.common.logging.Loggers; 30 | import org.elasticsearch.search.facet.FacetExecutor; 31 | import org.elasticsearch.search.facet.InternalFacet; 32 | import org.elasticsearch.search.internal.SearchContext; 33 | 34 | /** 35 | * Defines the content and abilities of a facet class. 36 | */ 37 | public class TermListFacetExecutor extends FacetExecutor { 38 | protected final ESLogger logger = Loggers.getLogger(getClass()); 39 | private List fields; 40 | private String search; 41 | private boolean prefix; 42 | private Collection uniqueTerms; 43 | private final int maxPerShard; 44 | private boolean caseInsensitive; 45 | private boolean sort; 46 | 47 | 48 | public TermListFacetExecutor(String facetName, List fields, String search, boolean prefix, SearchContext sc, int maxPerShard, boolean caseInsensitive, boolean sort) { 49 | logger.debug("TermListFacetExecutor : constructor : START {} : {} : {} ", facetName , fields , search); 50 | 51 | this.fields = fields; 52 | this.maxPerShard = maxPerShard; 53 | this.search = search; 54 | this.prefix = prefix; 55 | this.caseInsensitive = caseInsensitive; 56 | this.uniqueTerms = new HashSet(); 57 | this.sort = sort; 58 | 59 | logger.debug("TermListFacetExecutor : constructor : END "); 60 | } 61 | 62 | @Override 63 | public InternalFacet buildFacet(String facetName) { 64 | logger.debug("TermListFacetExecutor : buildFacet : CALLED {} : {} " , facetName , uniqueTerms); 65 | 66 | return new InternalTermListFacet(facetName, uniqueTerms.toArray(), sort); 67 | } 68 | 69 | @Override 70 | public Collector collector() { 71 | logger.debug("TermListFacetExecutor : collector : CALLED "); 72 | 73 | return new MyCollector(fields, search, caseInsensitive); 74 | } 75 | 76 | public class MyCollector extends FacetExecutor.Collector { 77 | private List fields; 78 | private String search; 79 | private boolean caseInsensitive; 80 | 81 | public MyCollector(List fields, String search, boolean caseInsensitive) { 82 | this.fields = fields; 83 | this.search = search; 84 | this.caseInsensitive = caseInsensitive; 85 | } 86 | 87 | @Override 88 | public void postCollection() { 89 | logger.debug("MyCollector : postCollection : CALLED"); 90 | 91 | // do nothing 92 | } 93 | 94 | @Override 95 | public void collect(int doc) throws IOException { 96 | logger.debug("MyCollector : collect : CALLED "); 97 | 98 | // do nothing 99 | } 100 | 101 | @Override 102 | public void setNextReader(AtomicReaderContext context) throws IOException { 103 | logger.debug("MyCollector : setNextReader : START : {} : {} " , search , fields ); 104 | 105 | if(logger.isDebugEnabled()) 106 | { 107 | Fields printDebugfields = context.reader().fields(); 108 | StringBuilder sb = new StringBuilder(); 109 | for (String field : printDebugfields) { 110 | sb.append(field); 111 | sb.append(", "); 112 | } 113 | logger.debug("Fields in this Context : [ {} ]", sb); 114 | } 115 | 116 | Terms terms = null; 117 | TermsEnum te = null; 118 | BytesRef byteRef = null; 119 | String formattedSearch = search; 120 | 121 | if(caseInsensitive && formattedSearch != null) 122 | { 123 | formattedSearch = search.toLowerCase(); 124 | } 125 | 126 | // two optional flags can control the matching done here 127 | // prefix - true/false to decide to match only the start of the terms 128 | // caseInsensitive - true/false do we care about the case of the search string and the term 129 | // 130 | 131 | outerloopoffields: 132 | for (String myfield : this.fields) { 133 | terms = context.reader().terms(myfield); 134 | 135 | if (terms != null) { 136 | te = terms.iterator(null); 137 | byteRef = null; 138 | 139 | while ((byteRef = te.next()) != null) { 140 | String termStr = new String(byteRef.bytes, byteRef.offset, byteRef.length); 141 | String formattedTerm = termStr; 142 | 143 | if(caseInsensitive) 144 | { 145 | formattedTerm = termStr.toLowerCase(); 146 | } 147 | 148 | if(logger.isDebugEnabled()) 149 | { 150 | logger.debug("MyCollector : setNextReader : {} : {} : {} : {} : {} : {} ", 151 | formattedTerm , 152 | formattedSearch , 153 | (formattedSearch!=null?formattedTerm.contains(formattedSearch):"null") , 154 | (formattedSearch!=null?formattedTerm.startsWith(formattedSearch):"null") , 155 | prefix , 156 | caseInsensitive); 157 | } 158 | 159 | if (uniqueTerms.size() < maxPerShard) { 160 | if (search != null) { 161 | if(prefix && formattedTerm.startsWith(formattedSearch)) 162 | { 163 | uniqueTerms.add(termStr); 164 | } else if (!prefix && formattedTerm.contains(formattedSearch)) { 165 | uniqueTerms.add(termStr); 166 | } 167 | } else { 168 | uniqueTerms.add(termStr); //everything matches, no search term 169 | } 170 | } else { 171 | logger.debug("BREAKING LOOP shardlimit hit : {} : {} " , maxPerShard , uniqueTerms.size()); 172 | break outerloopoffields; //lets get out of here, we have hit our max number 173 | } 174 | } 175 | } else { 176 | logger.debug("MyCollector : setNextReader : No terms found for field : {} ", myfield); 177 | } 178 | } 179 | 180 | logger.debug("MyCollector : setNextReader : EXIT "); 181 | } 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/search/facet/termlist/TermListFacetParser.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.elasticsearch.common.component.AbstractComponent; 23 | import org.elasticsearch.common.inject.Inject; 24 | import org.elasticsearch.common.settings.Settings; 25 | import org.elasticsearch.common.xcontent.XContentParser; 26 | import org.elasticsearch.index.mapper.FieldMapper; 27 | import org.elasticsearch.search.facet.FacetExecutor; 28 | import org.elasticsearch.search.facet.FacetExecutor.Mode; 29 | import org.elasticsearch.search.facet.FacetParser; 30 | import org.elasticsearch.search.facet.FacetPhaseExecutionException; 31 | import org.elasticsearch.search.internal.SearchContext; 32 | 33 | public class TermListFacetParser extends AbstractComponent implements FacetParser { 34 | 35 | /** 36 | * /** The type of the facet, for example, terms. 37 | * 38 | * String[] types(); 39 | * 40 | * 41 | * 42 | * /** The default mode to use when executed as a "main" (query level) 43 | * facet. 44 | * 45 | * FacetExecutor.Mode defaultMainMode(); 46 | * 47 | * /** The default mode to use when executed as a "global" (all docs) facet. 48 | * 49 | * FacetExecutor.Mode defaultGlobalMode(); 50 | * 51 | * /** Parses the facet into a {@link FacetExecutor}. 52 | * 53 | * FacetExecutor parse(String facetName, XContentParser parser, 54 | * SearchContext context) throws IOException; 55 | */ 56 | 57 | @Override 58 | public String[] types() { 59 | return new String[] { TermListFacet.TYPE, "term_list_facet" }; 60 | } 61 | 62 | /** 63 | * Instantiates a new term list facet processor. 64 | * 65 | * @param settings 66 | * the settings 67 | */ 68 | @Inject 69 | public TermListFacetParser(final Settings settings) { 70 | super(settings); 71 | InternalTermListFacet.registerStream(); 72 | } 73 | 74 | public FacetExecutor parse(String facetName, XContentParser parser, SearchContext context) throws IOException { 75 | List fields = null; 76 | XContentParser.Token token; 77 | String currentfieldName = null; 78 | String searchText = null; 79 | boolean prefix = false; 80 | boolean caseInsenstive = true; 81 | boolean sort = true; 82 | int maxPerShard = 100; 83 | 84 | while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { 85 | 86 | if (token == XContentParser.Token.FIELD_NAME) { 87 | currentfieldName = parser.currentName(); 88 | } else if (token.isValue()) { 89 | if ("max_per_shard".equals(currentfieldName)) { 90 | maxPerShard = parser.intValue(); 91 | } else if ("search".equals(currentfieldName)) { 92 | searchText = parser.text(); 93 | } else if ("prefix".equals(currentfieldName)) { 94 | prefix = parser.booleanValue(); 95 | } else if ("case_insenstive".equals(currentfieldName)) { 96 | caseInsenstive = parser.booleanValue(); 97 | } else if ("sort".equals(currentfieldName)) { 98 | sort = parser.booleanValue(); 99 | } 100 | } else if (token == XContentParser.Token.START_ARRAY) { 101 | if ("fields".equals(currentfieldName)) { 102 | fields = new ArrayList(); 103 | while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { 104 | fields.add(parser.text()); 105 | } 106 | } 107 | } 108 | } 109 | 110 | //a field is required 111 | if (fields == null || fields.isEmpty()) { 112 | throw new FacetPhaseExecutionException(facetName, "fields is required to be set for term list facet, either using [fields]"); 113 | } 114 | 115 | // check fields for correct mapping 116 | for (String field : fields) { 117 | final FieldMapper mapper = context.smartNameFieldMapper(field); 118 | if (mapper == null) { 119 | logger.warn("No mapping found for Field : {} ", field); 120 | throw new FacetPhaseExecutionException(facetName, "(key) field [" + field + "] not found"); 121 | 122 | } 123 | 124 | if (!"string".equals(mapper.fieldDataType().getType())) { 125 | logger.warn("No String mapping found for Field : {} ", field); 126 | throw new FacetPhaseExecutionException(facetName, "No String mapping found for field [" + field + "] not found"); 127 | } 128 | } 129 | 130 | return new TermListFacetExecutor(facetName, fields, searchText, prefix, context, maxPerShard, caseInsenstive, sort); 131 | } 132 | 133 | @Override 134 | public Mode defaultMainMode() { 135 | return FacetExecutor.Mode.COLLECTOR; 136 | } 137 | 138 | @Override 139 | public Mode defaultGlobalMode() { 140 | return FacetExecutor.Mode.COLLECTOR; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/resources/es-plugin.properties: -------------------------------------------------------------------------------- 1 | plugin=org.elasticsearch.plugin.termlist.FacetPlugin 2 | -------------------------------------------------------------------------------- /src/test/java/org/elasticsearch/search/facet/termlist/TermListFacetTest.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Endgame, Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, 10 | * software distributed under the License is distributed on an 11 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | * KIND, either express or implied. See the License for the 13 | * specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package org.elasticsearch.search.facet.termlist; 17 | 18 | import static org.elasticsearch.node.NodeBuilder.nodeBuilder; 19 | 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.HashMap; 24 | import java.util.HashSet; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Random; 28 | import java.util.Set; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | 31 | import junit.framework.TestCase; 32 | 33 | import org.elasticsearch.action.ListenableActionFuture; 34 | import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; 35 | import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; 36 | import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; 37 | import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; 38 | import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; 39 | import org.elasticsearch.action.admin.indices.flush.FlushRequest; 40 | import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; 41 | import org.elasticsearch.action.search.SearchRequestBuilder; 42 | import org.elasticsearch.action.search.SearchResponse; 43 | import org.elasticsearch.action.search.SearchType; 44 | import org.elasticsearch.client.Client; 45 | import org.elasticsearch.client.transport.TransportClient; 46 | import org.elasticsearch.common.settings.ImmutableSettings; 47 | import org.elasticsearch.common.settings.Settings; 48 | import org.elasticsearch.common.transport.InetSocketTransportAddress; 49 | import org.elasticsearch.common.xcontent.XContentBuilder; 50 | import org.elasticsearch.common.xcontent.XContentFactory; 51 | import org.elasticsearch.node.Node; 52 | 53 | /** 54 | * TermListFacetTest 55 | * 56 | * 57 | */ 58 | public class TermListFacetTest extends TestCase { 59 | private Node node; 60 | private Node node_two; 61 | private final String index = "test_index_lower"; 62 | private final String index_mixed = "test_index_mixed"; 63 | private final String facetName = "term_list_facet"; 64 | private static final AtomicInteger counter = new AtomicInteger(0); 65 | private final boolean uselocalhost = false; // helper method for client() will connect you to localhost if this is true 66 | private final Random _random = new Random(0); 67 | private final int randomElementsMultiplier = 100; //use this to help control how many random elements to create 68 | private final int numOfElements = randomElementsMultiplier + _random.nextInt(100); 69 | private final int numberOfShards = 1; 70 | private final int numberOfReplicas = 0; 71 | private List testFields_name = new ArrayList(); 72 | private List testFields_childName = new ArrayList(); 73 | private List testFields_nameAndChildName = new ArrayList(); 74 | private List testFields_childrenName = new ArrayList(); 75 | private List parentRandomStrings = new ArrayList(); 76 | private List childRandomStrings = new ArrayList(); 77 | private List parentAndChildRandomStrings = new ArrayList(); 78 | private Set uniqParentText = new HashSet(); 79 | private Set uniqChildText = new HashSet(); 80 | private Set uniqAllText = new HashSet(); 81 | 82 | @Override 83 | protected void tearDown() throws Exception { 84 | super.tearDown(); 85 | 86 | deleteAllIndices(); 87 | 88 | node.close(); 89 | node_two.close(); 90 | if(!node.isClosed()){ 91 | System.out.println("node close did not work : "); 92 | throw new Exception("node close did not work : "); 93 | } 94 | } 95 | 96 | @Override 97 | protected void setUp() throws Exception { 98 | super.setUp(); 99 | 100 | testFields_name.add("name"); 101 | testFields_nameAndChildName.add("name"); 102 | testFields_nameAndChildName.add("child.name"); 103 | testFields_childName.add("child.name"); 104 | testFields_childrenName.add("children.name"); 105 | 106 | Settings settings = ImmutableSettings.settingsBuilder() 107 | .put("node.http.enabled", true) 108 | .put("index.gateway.type", "none") 109 | .put("index.number_of_shards", numberOfShards) 110 | .put("index.number_of_replicas", numberOfReplicas) 111 | .put("path.data", "target") 112 | .put("refresh_interval", -1) 113 | .put("node.name", "chicken_1") 114 | .put("index.cache.field.type", "soft").build(); 115 | 116 | node = nodeBuilder().local(true).settings(settings).clusterName("TermListFacetTest").node(); 117 | node.start(); 118 | 119 | // wait for green after start 120 | // 121 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 122 | 123 | Settings settings_two = ImmutableSettings.settingsBuilder() 124 | .put("node.http.enabled", true) 125 | .put("index.gateway.type", "none") 126 | .put("index.number_of_shards", numberOfShards) 127 | .put("index.number_of_replicas", numberOfReplicas) 128 | .put("path.data", "target") 129 | .put("refresh_interval", -1) 130 | .put("node.name", "chicken_2") 131 | .put("index.cache.field.type", "soft").build(); 132 | 133 | node_two = nodeBuilder().local(true).settings(settings_two).clusterName("TermListFacetTest").node(); 134 | node_two.start(); 135 | 136 | // wait for green after start 137 | // 138 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 139 | 140 | // set up rules for analysis, we want lowercase, keyword tokenization 141 | // (do not break up spaces) 142 | // 143 | XContentBuilder analysis = XContentFactory.jsonBuilder().startObject() 144 | .startObject("analysis") 145 | .startObject("analyzer") 146 | .startObject("default") 147 | .field("type", "custom") 148 | .field("tokenizer", "keyword") 149 | .field("filter", new String[] { "lowercase" }).endObject().endObject().endObject().endObject(); 150 | 151 | XContentBuilder analysis_mixedcase = XContentFactory.jsonBuilder().startObject() 152 | .startObject("analysis") 153 | .startObject("analyzer") 154 | .startObject("default") 155 | .field("type", "custom") 156 | .field("tokenizer", "keyword") 157 | .field("filter", new String[] { "standard" }) 158 | .endObject() 159 | .endObject() 160 | .endObject() 161 | .endObject(); 162 | 163 | // wait for green after start 164 | // 165 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 166 | 167 | // this create/delete error checking is a little paranoid, but I experienced some strange node not re-creating index still exists 168 | // while constructing tests and running them inside ide so this is here just to be really sure indexes are created correctly for each test 169 | // 170 | try{ 171 | CreateIndexResponse cir = client().admin().indices().prepareCreate(index).setSettings(analysis.string()).execute().actionGet(); 172 | CreateIndexResponse cir_2 = client().admin().indices().prepareCreate(index_mixed).setSettings(analysis_mixedcase.string()).execute().actionGet(); 173 | 174 | if(!cir.isAcknowledged()){ 175 | System.out.println("Create was not acknowledged : " + index_mixed); 176 | throw new Exception("Failed Create of Index : " + index_mixed); 177 | } 178 | 179 | if(!cir_2.isAcknowledged()){ 180 | System.out.println("Create was not acknowledged : " + index_mixed); 181 | throw new Exception("Failed Create of Index : " + index_mixed); 182 | } 183 | 184 | // wait for green after index create 185 | // 186 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 187 | 188 | } catch (Exception e) { // be sure to tear down if we fail during setup, to prevent index already created issues from repeating 189 | deleteAllIndices(); 190 | System.out.println("Error during Setup : " + e.toString()); 191 | throw new Exception("Error during Setup : ", e); 192 | } 193 | } 194 | 195 | /*************************************************************************************************/ 196 | 197 | /** 198 | * testNonStringTermsShouldThrowError 199 | * 200 | * @throws Exception 201 | */ 202 | public void testNonStringTermsShouldThrowError() throws Exception { 203 | runStandardPutsAndFlush(index); //include numeric data 204 | List randomNumField = new ArrayList(); 205 | randomNumField.add("rnum"); 206 | boolean thrown = false; 207 | 208 | try { 209 | TermListFacetBuilder custom_facet = new TermListFacetBuilder(facetName).fields(randomNumField); 210 | 211 | SearchRequestBuilder srb = client().prepareSearch(index); 212 | srb.setSearchType(SearchType.COUNT); 213 | srb.addFacet(custom_facet); 214 | 215 | System.out.println("SearchResponse Facet : \n " + srb.toString() + "\n"); 216 | 217 | ListenableActionFuture laf = srb.execute(); 218 | SearchResponse sr = laf.actionGet(); 219 | 220 | fail("Never should have gotten here: "); 221 | } catch (Exception e) { 222 | e.printStackTrace(System.out); 223 | thrown = true; 224 | } 225 | 226 | assertTrue(thrown); 227 | } 228 | 229 | /**** 230 | * testMissingRequiredSearchTermsThrowsError 231 | * 232 | * Test the required params "field" 233 | */ 234 | public void testMissingRequiredSearchTermsThrowsError() throws Exception { 235 | //build facet without fields param 236 | // 237 | TermListFacetBuilder custom_facet = new TermListFacetBuilder(facetName); 238 | boolean thrown = false; 239 | 240 | try { 241 | SearchRequestBuilder srb = client().prepareSearch(index); 242 | srb.setSearchType(SearchType.COUNT); 243 | srb.addFacet(custom_facet); 244 | ListenableActionFuture laf = srb.execute(); 245 | fail("Never should have gotten here: "); 246 | 247 | } catch (Exception e) { 248 | thrown = true; 249 | } 250 | 251 | assertTrue(thrown); 252 | } 253 | 254 | /** 255 | * testWithOnlyRequiredParams 256 | * 257 | * @throws Exception 258 | */ 259 | public void testWithOnlyRequiredParams() throws Exception { 260 | runStandardPutsAndFlush(index); 261 | SearchResponse response = this.getTermList(index, testFields_name); 262 | 263 | // did the number of hits from es match the number we put in? 264 | // 265 | assertEquals(numOfElements, countAll(index)); 266 | assertEquals(numOfElements, response.getHits().getTotalHits()); 267 | 268 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 269 | 270 | assertTrue(entries.size() > 0); 271 | 272 | for (Object item : entries) { 273 | assertTrue( parentRandomStrings.contains(item.toString())); 274 | } 275 | } 276 | 277 | /** 278 | * testShardLimit 279 | * 280 | * @throws Exception 281 | */ 282 | public void testShardLimit() throws Exception { 283 | runStandardPutsAndFlush(index); 284 | SearchResponse response = this.getTermList(index, testFields_name, "a", 1, false, true, true); 285 | 286 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 287 | 288 | // did the number of hits from es match the number we put in? 289 | // 290 | assertEquals(numOfElements, countAll(index)); 291 | assertEquals(numOfElements, response.getHits().getTotalHits()); 292 | 293 | // did the number of hits from es match the number of shards (since we asked for only 1 from each shard) 294 | // 295 | assertEquals(this.numberOfShards, entries.size()); 296 | 297 | } 298 | 299 | /**** 300 | * Test the optional search parameters, prefix and case insensitive 301 | */ 302 | 303 | /** 304 | * testSearchWithPrefix 305 | * 306 | * @throws Exception 307 | */ 308 | public void testSearchWithPrefix() throws Exception { 309 | runStandardPutsAndFlush(index); 310 | SearchResponse response = this.getTermList(index, testFields_name, "a", 10000, true, true, true); 311 | 312 | // did the number of hits from es match the number we put in? 313 | // 314 | assertEquals(numOfElements, countAll(index)); 315 | assertEquals(numOfElements, response.getHits().getTotalHits()); 316 | 317 | checkPrefixSearchResults(response, parentRandomStrings, "a"); 318 | } 319 | 320 | /** 321 | * testSearch 322 | * 323 | * @throws Exception 324 | */ 325 | public void testSearch() throws Exception { 326 | runStandardPutsAndFlush(index); 327 | SearchResponse response = this.getTermList(index, testFields_name, "z", 10000, false, true, true); 328 | 329 | // did the number of hits from es match the number we put in? 330 | // 331 | assertEquals(numOfElements, countAll(index)); 332 | assertEquals(numOfElements, response.getHits().getTotalHits()); 333 | 334 | checkContainsSearchResults(response, parentRandomStrings, "z"); 335 | } 336 | 337 | /** 338 | * testSearchWithCaseSensitive 339 | * 340 | * @throws Exception 341 | */ 342 | public void testSearchWithCaseSensitive() throws Exception { 343 | runStandardPutsAndFlush(index_mixed); 344 | SearchResponse response = this.getTermList(index_mixed, testFields_name, "C", 10000, false, false, true); 345 | 346 | // did the number of hits from es match the number we put in? 347 | // 348 | assertEquals(numOfElements, countAll(index_mixed)); 349 | assertEquals(numOfElements, response.getHits().getTotalHits()); 350 | 351 | checkContainsSearchResults(response, parentRandomStrings, "C"); 352 | } 353 | 354 | /** 355 | * testPrefixWithCaseSensitive 356 | * 357 | * @throws Exception 358 | */ 359 | public void testPrefixWithCaseSensitive() throws Exception { 360 | runStandardPutsAndFlush(index_mixed); 361 | SearchResponse response = this.getTermList(index_mixed, testFields_name, "C", 10000, true, false, true); 362 | 363 | // did the number of hits from es match the number we put in? 364 | // 365 | assertEquals(numOfElements, countAll(index_mixed)); 366 | assertEquals(numOfElements, response.getHits().getTotalHits()); 367 | 368 | checkPrefixSearchResults(response, parentRandomStrings, "C"); 369 | } 370 | 371 | /********** 372 | * Test for parent level field, sub object/child field, and both 373 | */ 374 | 375 | /** 376 | * testParentLevelFieldOfNestedObjects 377 | * 378 | * @throws Exception 379 | */ 380 | public void testParentLevelFieldOfNestedObjects() throws Exception { 381 | runStandardPutsAndFlush(index); 382 | SearchResponse response = this.getTermList(index, testFields_name, null, 10000, false, true, true); 383 | 384 | // did the number of hits from es match the number we put in? 385 | // 386 | assertEquals(numOfElements, countAll(index)); 387 | assertEquals(numOfElements, response.getHits().getTotalHits()); 388 | 389 | checkStringSearchResponse(response, numOfElements, uniqParentText.size(), parentRandomStrings); 390 | } 391 | 392 | /** 393 | * testChildLevelFieldOfNestedObjects 394 | * 395 | * @throws Exception 396 | */ 397 | public void testChildLevelFieldOfNestedObjects() throws Exception { 398 | runStandardPutsAndFlush(index); 399 | SearchResponse response = this.getTermList(index, testFields_childName, null, 10000, false, true, true); 400 | 401 | // did the number of hits from es match the number we put in? 402 | // 403 | assertEquals(numOfElements, countAll(index)); 404 | assertEquals(numOfElements, response.getHits().getTotalHits()); 405 | 406 | checkStringSearchResponse(response, numOfElements, uniqChildText.size(), childRandomStrings); 407 | } 408 | 409 | /** 410 | * testArrayOfChildrenFieldOfNestedObjects 411 | * can we locate terms that are nested inside an array 412 | * 413 | * @throws Exception 414 | */ 415 | public void testArrayOfChildrenFieldOfNestedObjects() throws Exception { 416 | runStandardPutsAndFlush(index); 417 | SearchResponse response = this.getTermList(index, testFields_childrenName, "a", 10000, false, true, true); 418 | 419 | // did the number of hits from es match the number we put in? 420 | // 421 | assertEquals(numOfElements, countAll(index)); 422 | assertEquals(numOfElements, response.getHits().getTotalHits()); 423 | 424 | checkContainsSearchResults(response, childRandomStrings, "a"); 425 | } 426 | 427 | /** 428 | * testChildAndParentLevelFieldOfNestedObjects 429 | * 430 | * @throws Exception 431 | */ 432 | public void testChildAndParentLevelFieldOfNestedObjects() throws Exception { 433 | runStandardPutsAndFlush(index); 434 | SearchResponse response = this.getTermList(index, testFields_nameAndChildName, null, 10000, false, true, true); 435 | 436 | // did the number of hits from es match the number we put in? 437 | // 438 | assertEquals(numOfElements, countAll(index)); 439 | assertEquals(numOfElements, response.getHits().getTotalHits()); 440 | 441 | checkStringSearchResponse(response, numOfElements, uniqAllText.size(), parentAndChildRandomStrings); 442 | } 443 | 444 | /** 445 | * testNoSort 446 | * 447 | * @throws Exception 448 | */ 449 | public void testNoSort() throws Exception { 450 | runStandardPutsAndFlush(index); 451 | SearchResponse response = this.getTermList(index, testFields_name, null, 10000, false, true, false); 452 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 453 | 454 | // did the number of hits from es match the number we put in? 455 | // 456 | assertEquals(numOfElements, countAll(index)); 457 | assertEquals(numOfElements, response.getHits().getTotalHits()); 458 | 459 | // are we unsorted 460 | List copy = new ArrayList(entries); 461 | Collections.sort(copy); 462 | assertEquals(false, copy.equals(entries)); 463 | } 464 | 465 | 466 | 467 | /** 468 | * testSort 469 | * 470 | * @throws Exception 471 | */ 472 | public void testSort() throws Exception { 473 | runStandardPutsAndFlush(index); 474 | SearchResponse response = this.getTermList(index, testFields_name, null, 10000, false, true, true); 475 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 476 | 477 | // did the number of hits from es match the number we put in? 478 | // 479 | assertEquals(numOfElements, countAll(index)); 480 | assertEquals(numOfElements, response.getHits().getTotalHits()); 481 | 482 | // are we sorted 483 | List copy = new ArrayList(entries); 484 | Collections.sort(copy); 485 | assertEquals(copy, entries); 486 | } 487 | 488 | /** 489 | * checkContainsSearchResults 490 | * 491 | * @param response 492 | * @param putStringList 493 | * @param searchTerm 494 | */ 495 | private void checkContainsSearchResults(SearchResponse response, List putStringList, String searchTerm) 496 | { 497 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 498 | 499 | // is each member of the facet list a member of the list we sent in? 500 | // does each member of the facet list contain our search term 501 | for (Object item : entries) { 502 | assertTrue( putStringList.contains(item.toString())); 503 | assertTrue( item.toString().contains(searchTerm)); 504 | } 505 | } 506 | 507 | /** 508 | * checkPrefixSearchResults 509 | * 510 | * @param response 511 | * @param putStringList 512 | * @param searchTerm 513 | */ 514 | private void checkPrefixSearchResults(SearchResponse response, List putStringList, String searchTerm) 515 | { 516 | List entries = ((TermListFacet) response.getFacets().facet(facetName)).entries(); 517 | 518 | // is each member of the facet list a member of the list we sent in? 519 | // does each member of the facet list start with the search term 520 | for (Object item : entries) { 521 | assertTrue( putStringList.contains(item.toString())); 522 | assertTrue( item.toString().startsWith(searchTerm)); 523 | } 524 | } 525 | 526 | /** 527 | * checkStringSearchResponse 528 | * 529 | * Does the response contain the correct number of 530 | * unique items? Are the values in the response ones that we placed in ES? 531 | * 532 | * @param sr 533 | * @param numOfDocs 534 | * @param numOfElements 535 | * @param words 536 | * 537 | */ 538 | private void checkStringSearchResponse(final SearchResponse sr, final int numOfElements, final int numOfUniqueElements, final List allPossibleWords) { 539 | TermListFacet facet = sr.getFacets().facet(facetName); 540 | final List entries = facet.entries(); 541 | final int len = entries.size(); 542 | 543 | // is our results list as long as the unique list we sent in? 544 | // 545 | assertEquals(len, numOfUniqueElements); 546 | 547 | // is each member of the facet list a member of the list we sent in? 548 | // 549 | for (final Object item : entries) 550 | assertTrue( allPossibleWords.contains(item.toString())); 551 | 552 | } 553 | 554 | /*****************************************************************************************************/ 555 | 556 | /** 557 | * deleteAllIndices 558 | * 559 | * @throws Exception 560 | */ 561 | private void deleteAllIndices() throws Exception 562 | { 563 | IndicesExistsResponse af = client().admin().indices().exists(new IndicesExistsRequest(index)).actionGet(); 564 | if(af.isExists()) { 565 | DeleteIndexResponse dir = client().admin().indices().delete(new DeleteIndexRequest(index)).actionGet(); 566 | 567 | if(!dir.isAcknowledged()){ 568 | System.out.println("Delete was not acknowledged" + index); 569 | throw new Exception("Failed Delete of Index : " + index); 570 | } 571 | } 572 | 573 | IndicesExistsResponse af_2 = client().admin().indices().exists(new IndicesExistsRequest(index_mixed)).actionGet(); 574 | if(af_2.isExists()) { 575 | DeleteIndexResponse dir_2 = client().admin().indices().delete(new DeleteIndexRequest(index_mixed)).actionGet(); 576 | 577 | if(!dir_2.isAcknowledged()){ 578 | System.out.println("Delete was not acknowledged : " + index_mixed); 579 | throw new Exception("Failed Delete of Index : " + index_mixed); 580 | } 581 | } 582 | 583 | // wait for green after delete 584 | // 585 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 586 | } 587 | 588 | /** 589 | * runStandardPutsAndFlush 590 | * generate random data, count it for comparison later 591 | * call es with puts of the generated data 592 | * 593 | * @param index 594 | * @throws IOException 595 | */ 596 | /*public void runStandardPutsAndFlush(String index) throws IOException 597 | { 598 | this.runStandardPutsAndFlush(index, true); 599 | }*/ 600 | 601 | public void runStandardPutsAndFlush(String index) throws IOException 602 | { 603 | parentRandomStrings = new ArrayList(); 604 | childRandomStrings = new ArrayList(); 605 | parentAndChildRandomStrings = new ArrayList(); 606 | 607 | parentRandomStrings = this.generateRandomStrings(numOfElements); 608 | childRandomStrings = this.generateRandomStrings(numOfElements); 609 | 610 | List> listofmaps = this.generateSimpleNestedData(parentRandomStrings, childRandomStrings, numOfElements); 611 | 612 | for (int i = 0; i < numOfElements; i++) { 613 | this.putSimpleNestedData(index, "test_type", listofmaps.get(i)); 614 | } 615 | 616 | for (String s : parentRandomStrings) { 617 | uniqParentText.add(s); 618 | } 619 | 620 | for (String s : childRandomStrings) { 621 | uniqChildText.add(s); 622 | } 623 | 624 | uniqAllText.addAll(uniqParentText); 625 | uniqAllText.addAll(uniqChildText); 626 | 627 | parentAndChildRandomStrings.addAll(parentRandomStrings); 628 | parentAndChildRandomStrings.addAll(childRandomStrings); 629 | 630 | flush(index); 631 | } 632 | 633 | /** 634 | * getTermList 635 | * 636 | * @param index 637 | * @param keyFields 638 | * @param searchText 639 | * @param maxPerShard 640 | * @param prefix 641 | * @return 642 | */ 643 | private SearchResponse getTermList(String index, List fields, String searchText, int maxPerShard, boolean prefix, boolean caseInsensitive, boolean sort) { 644 | 645 | TermListFacetBuilder custom_facet = new TermListFacetBuilder(facetName).fields(fields).maxPerShard(maxPerShard).prefix(prefix).sort(sort).caseInsensitive(caseInsensitive).search(searchText); 646 | SearchResponse custom_sr = null; 647 | 648 | try { 649 | SearchRequestBuilder srb = client().prepareSearch(index); 650 | srb.setSearchType(SearchType.COUNT); 651 | srb.addFacet(custom_facet); 652 | 653 | System.out.println("SearchRequestBuilder Facet : \n " + srb.toString() + "\n"); 654 | 655 | ListenableActionFuture laf = srb.execute(); 656 | custom_sr = laf.actionGet(); 657 | 658 | } catch (Exception e) { 659 | e.printStackTrace(System.out); 660 | fail("this test failed"); 661 | } 662 | 663 | assertFalse(custom_sr.toString().startsWith("{ \"error\" : ")); 664 | System.out.println("SearchResponse : \n " + custom_sr.toString()); 665 | return custom_sr; 666 | } 667 | 668 | /** 669 | * getTermList 670 | * 671 | * @param index 672 | * @param fields 673 | * @return 674 | */ 675 | private SearchResponse getTermList(String index, List fields) { 676 | TermListFacetBuilder custom_facet = new TermListFacetBuilder(facetName).fields(fields); 677 | SearchResponse custom_sr = null; 678 | 679 | try { 680 | SearchRequestBuilder srb = client().prepareSearch(index); 681 | srb.setSearchType(SearchType.COUNT); 682 | srb.addFacet(custom_facet); 683 | 684 | System.out.println("SearchRequestBuilder Facet : \n " + srb.toString() + "\n"); 685 | 686 | ListenableActionFuture laf = srb.execute(); 687 | custom_sr = laf.actionGet(); 688 | 689 | } catch (Exception e) { 690 | System.out.println("Exception e : " + e); 691 | StackTraceElement[] elements = e.getStackTrace(); 692 | 693 | for (int i = 0; i < elements.length; i++) { 694 | System.out.println(elements[i]); 695 | } 696 | 697 | fail("this test failed"); 698 | } 699 | 700 | System.out.println("SearchResponse : \n " + custom_sr.toString()); 701 | assertFalse(custom_sr.toString().startsWith("{ \"error\" : ")); 702 | return custom_sr; 703 | } 704 | 705 | /** 706 | * generateRandomStrings 707 | * 708 | * @param numberOfWords 709 | * @return 710 | */ 711 | private List generateRandomStrings(int numberOfWords) { 712 | return this.generateRandomStrings(numberOfWords, true); 713 | } 714 | 715 | /** 716 | * generateRandomStrings 717 | * 718 | * @param numberOfWords 719 | * @param lowercaseonly 720 | * @return 721 | */ 722 | private List generateRandomStrings(final int numberOfWords, boolean lowercaseonly) { 723 | final String[] randomStrings = new String[numberOfWords]; 724 | List myList = new ArrayList<>(); 725 | for (int i = 0; i < numberOfWords; i++) { 726 | final char[] word = new char[_random.nextInt(8) + 3]; 727 | 728 | for (int j = 0; j < word.length; j++) { 729 | if (lowercaseonly) { 730 | word[j] = (char) ('a' + _random.nextInt(26)); 731 | } else { 732 | if (_random.nextInt(10) > 5) { 733 | word[j] = (char) ('A' + _random.nextInt(26)); 734 | } else { 735 | word[j] = (char) ('a' + _random.nextInt(26)); 736 | } 737 | } 738 | } 739 | 740 | if (_random.nextInt(10) > 5) { 741 | final char[] word2 = new char[_random.nextInt(8) + 3]; 742 | 743 | for (int k = 0; k < word2.length; k++) { 744 | word2[k] = (char) ('a' + _random.nextInt(26)); 745 | } 746 | 747 | randomStrings[i] = new String(word) + " " + new String(word2); 748 | } else { 749 | randomStrings[i] = new String(word); 750 | } 751 | 752 | myList.add(randomStrings[i]); 753 | } 754 | 755 | return myList; 756 | } 757 | 758 | /** 759 | * putSimpleNestedData 760 | * 761 | * @param index 762 | * @param type 763 | * @param data 764 | * @throws IOException 765 | */ 766 | private void putSimpleNestedData(String index, String type, Map data) throws IOException { 767 | XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("myid", data.get("id")) 768 | .field("name", data.get("name")) 769 | .field("rnum", data.get("rnum")) 770 | .field("child", data.get("child")) 771 | .field("children", data.get("children")) 772 | .endObject(); 773 | 774 | //put the data in es 775 | client().prepareIndex(index, type, (String) data.get("id")).setRefresh(true).setRouting((String) data.get("id")).setSource(builder).execute().actionGet(); 776 | client().prepareGet(index, type, (String) data.get("id")).execute().actionGet(); 777 | } 778 | 779 | /** 780 | * @param index 781 | */ 782 | private void flush(String index) 783 | { 784 | // flush it to ensure data is present 785 | client().admin().indices().flush(new FlushRequest(index)).actionGet(); 786 | client().admin().indices().refresh(new RefreshRequest()).actionGet(); 787 | } 788 | 789 | /** 790 | * generateSimpleNestedData 791 | * 792 | * @param count 793 | * @return 794 | * @throws IOException 795 | */ 796 | private List> generateSimpleNestedData(List randomParentNames, List randomChildNames, int count) throws IOException { 797 | List> simpleNestedList = new ArrayList>(); 798 | 799 | for (int i = 0; i < randomParentNames.size(); i++) { 800 | String stringID = String.valueOf(newID()); 801 | Map parentData = new HashMap(); 802 | parentData.put("id", stringID); 803 | parentData.put("name", randomParentNames.get(i)); 804 | parentData.put("rnum", (Integer) _random.nextInt()); 805 | 806 | Map subData = new HashMap(); 807 | subData.put("name", randomChildNames.get(i)); 808 | 809 | Map subData2 = new HashMap(); 810 | subData.put("name", randomChildNames.get(i)); 811 | 812 | List> children = new ArrayList>(); 813 | children.add(subData); 814 | children.add(subData2); 815 | 816 | parentData.put("child", subData); 817 | parentData.put("children", children.toArray()); 818 | 819 | simpleNestedList.add(parentData); 820 | } 821 | 822 | return simpleNestedList; 823 | } 824 | 825 | private static int newID() { 826 | return counter.getAndIncrement(); 827 | } 828 | 829 | private long countAll(String index) { 830 | return client().prepareCount(index).execute().actionGet().getCount(); 831 | } 832 | 833 | private Client client() { 834 | if (uselocalhost) { 835 | return localclient(); 836 | } else { 837 | return node.client(); 838 | } 839 | } 840 | 841 | /** 842 | * helper if you want to hit local es install to do some testing 843 | * 844 | * @return 845 | */ 846 | private Client localclient() { 847 | Settings settings = ImmutableSettings.settingsBuilder().put("cluster.name", "elasticsearch").build(); 848 | return new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress("localhost", 9300)); 849 | } 850 | 851 | } 852 | --------------------------------------------------------------------------------