lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gerlowsk...@apache.org
Subject [1/2] lucene-solr:branch_7x: SOLR-12965: Add facet support to JsonQueryRequest
Date Wed, 14 Nov 2018 20:28:19 GMT
Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x 6faddfe3b -> b502ba288


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
new file mode 100644
index 0000000..be4f0cc
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
@@ -0,0 +1,615 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.UpdateResponse;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.ExternalPaths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class DirectJsonQueryRequestFacetingIntegrationTest extends SolrCloudTestCase {
+
+  private static final String COLLECTION_NAME = "techproducts";
+  private static final String CONFIG_NAME = "techproducts_config";
+  private static final int NUM_TECHPRODUCTS_DOCS = 32;
+  private static final int NUM_IN_STOCK = 17;
+  private static final int NUM_ELECTRONICS = 12;
+  private static final int NUM_CURRENCY = 4;
+  private static final int NUM_MEMORY = 3;
+  private static final int NUM_CORSAIR = 3;
+  private static final int NUM_BELKIN = 2;
+  private static final int NUM_CANON = 2;
+
+
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(1)
+        .addConfig(CONFIG_NAME, new File(ExternalPaths.TECHPRODUCTS_CONFIGSET).toPath())
+        .configure();
+
+    final List<String> solrUrls = new ArrayList<>();
+    solrUrls.add(cluster.getJettySolrRunner(0).getBaseUrl().toString());
+
+    CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1).process(cluster.getSolrClient());
+
+    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    up.setParam("collection", COLLECTION_NAME);
+    up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
+    up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+    UpdateResponse updateResponse = up.process(cluster.getSolrClient());
+    assertEquals(0, updateResponse.getStatus());
+  }
+  @Test
+  public void testSingleTermsFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testMultiTermsFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "    },",
+        "    'top_manufacturers': {",
+        "      'type': 'terms',",
+        "      'field': 'manu_id_s',",
+        "      'limit': 3",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+    assertHasFacetWithBucketValues(rawResponse,"top_manufacturers", new FacetBucket("corsair",NUM_CORSAIR),
+        new FacetBucket("belkin", NUM_BELKIN), new FacetBucket("canon", NUM_CANON));
+  }
+
+  @Test
+  public void testSingleRangeFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'prices': {",
+        "      'type': 'range',",
+        "      'field': 'price',",
+        "      'start': 0,",
+        "      'end': 100,",
+        "      'gap': 20",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"prices",
+        new FacetBucket(0.0f, 5),
+        new FacetBucket(20.0f, 0),
+        new FacetBucket(40.0f, 0),
+        new FacetBucket(60.0f, 1),
+        new FacetBucket(80.0f, 1));
+  }
+
+  @Test
+  public void testMultiRangeFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'prices': {",
+        "      'type': 'range',",
+        "      'field': 'price',",
+        "      'start': 0,",
+        "      'end': 100,",
+        "      'gap': 20",
+        "    },",
+        "    'shipping_weights': {",
+        "      'type': 'range',",
+        "      'field': 'weight',",
+        "      'start': 0,",
+        "      'end': 200,",
+        "      'gap': 50",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"prices",
+        new FacetBucket(0.0f, 5),
+        new FacetBucket(20.0f, 0),
+        new FacetBucket(40.0f, 0),
+        new FacetBucket(60.0f, 1),
+        new FacetBucket(80.0f, 1));
+    assertHasFacetWithBucketValues(rawResponse, "shipping_weights",
+        new FacetBucket(0.0f, 6),
+        new FacetBucket(50.0f, 0),
+        new FacetBucket(100.0f, 0),
+        new FacetBucket(150.0f,1));
+  }
+
+  @Test
+  public void testSingleStatFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'sum_price': 'sum(price)'",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+  }
+
+  @Test
+  public void testMultiStatFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'sum_price': 'sum(price)',",
+        "    'avg_price': 'avg(price)'",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+    assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+  }
+
+  @Test
+  public void testMultiFacetsMixedTypes() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'avg_price': 'avg(price)',",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testNestedTermsFacet() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "      'facet': {",
+        "        'top_manufacturers_for_cat': {",
+        "          'type': 'terms',",
+        "          'field': 'manu_id_s',",
+        "          'limit': 1",
+        "        }",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+
+    // Test subfacet values for each top-level facet bucket
+    final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+    final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+    assertFacetResponseHasFacetWithBuckets(electronicsSubFacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+    final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+    assertFacetResponseHasFacetWithBuckets(currencySubfacet, "top_manufacturers_for_cat", new FacetBucket("boa", 1));
+    final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+    assertFacetResponseHasFacetWithBuckets(memorySubfacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+  }
+
+  @Test
+  public void testNestedFacetsOfMixedTypes() throws Exception {
+    final String subfacetName = "avg_price_for_cat";
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "      'facet': {",
+        "        'avg_price_for_cat': 'avg(price)'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+
+    // Test subfacet values for each top-level facet bucket
+    final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+    final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+    assertFacetResponseHasStatFacetWithValue(electronicsSubFacet, subfacetName, 252.02909261530095);
+    final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+    assertFacetResponseHasStatFacetWithValue(currencySubfacet, subfacetName, 0.0);
+    final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+    assertFacetResponseHasStatFacetWithValue(memorySubfacet, subfacetName, 129.99499893188477);
+  }
+
+  @Test
+  public void testFacetWithDomainFilteredBySimpleQueryString() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_popular_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "      'domain': {",
+        "        'filter': 'popularity:[5 TO 10]'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+        new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+  }
+
+  @Test
+  public void testFacetWithDomainFilteredByLocalParamsQueryString() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'facet': {",
+        "    'top_popular_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "      'domain': {",
+        "        'filter': '{!lucene df=\"popularity\" v=\"[5 TO 10]\"}'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+        new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+  }
+
+  @Test
+  public void testFacetWithArbitraryDomainFromQueryString() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': 'cat:electronics',",
+        "  'facet': {",
+        "    'top_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 3",
+        "      'domain': {",
+        "        'query': '*:*'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testFacetWithArbitraryDomainFromLocalParamsQuery() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': 'cat:electronics',",
+        "  'facet': {",
+        "    'largest_search_cats': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'domain': {",
+        "        'query': '{!lucene df=\"cat\" v=\"search\"}'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    assertHasFacetWithBucketValues(rawResponse,"largest_search_cats",
+        new FacetBucket("search",2),
+        new FacetBucket("software", 2));
+  }
+
+  /*
+   * Multiple query clauses are effectively AND'd together
+   */
+  public void testFacetWithMultipleSimpleQueryClausesInArbitraryDomain() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': 'cat:electronics',",
+        "  'facet': {",
+        "    'cats_matching_solr': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'domain': {",
+        "        'query': ['cat:search', 'name:Solr']",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+        new FacetBucket("search",1),
+        new FacetBucket("software", 1));
+  }
+
+  public void testFacetWithMultipleLocalParamsQueryClausesInArbitraryDomain() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': 'cat:electronics',",
+        "  'facet': {",
+        "    'cats_matching_solr': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'domain': {",
+        "        'query': ['{!lucene df=\"cat\" v=\"search\"}', '{!lucene df=\"name\" v=\"Solr\"}']",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+        new FacetBucket("search",1),
+        new FacetBucket("software", 1));
+  }
+
+  @Test
+  public void testFacetWithDomainWidenedUsingExcludeTagsToIgnoreFilters() throws Exception {
+    final String jsonBody = String.join("\n","{",
+        "  'query': '*:*',",
+        "  'filter': {'#on_shelf': 'inStock:true'},",
+        "  'facet': {",
+        "    'in_stock_only': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 2",
+        "    }",
+        "    'all': {",
+        "      'type': 'terms',",
+        "      'field': 'cat',",
+        "      'limit': 2,",
+        "      'domain': {",
+        "        'excludeTags': 'on_shelf'",
+        "      }",
+        "    }",
+        "  }",
+        "}");
+    final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_IN_STOCK, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+
+    assertHasFacetWithBucketValues(rawResponse,"in_stock_only",
+        new FacetBucket("electronics",8),
+        new FacetBucket("currency", 4));
+    assertHasFacetWithBucketValues(rawResponse,"all",
+        new FacetBucket("electronics",12),
+        new FacetBucket("currency", 4));
+  }
+
+  private class FacetBucket {
+    private final Object val;
+    private final int count;
+    FacetBucket(Object val, int count) {
+      this.val = val;
+      this.count = count;
+    }
+
+    public Object getVal() { return val; }
+    public int getCount() { return count; }
+  }
+
+  private void assertHasFacetWithBucketValues(NamedList<Object> rawResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+    final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+    assertFacetResponseHasFacetWithBuckets(facetsTopLevel, expectedFacetName, expectedBuckets);
+  }
+
+  private void assertHasStatFacetWithValue(NamedList<Object> rawResponse, String expectedFacetName, Double expectedStatValue) {
+    final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+    assertFacetResponseHasStatFacetWithValue(facetsTopLevel, expectedFacetName, expectedStatValue);
+  }
+
+  private NamedList<Object> assertHasFacetResponse(NamedList<Object> topLevelResponse) {
+    Object o = topLevelResponse.get("facets");
+    if (o == null) fail("Response has no top-level 'facets' property as expected");
+    if (!(o instanceof NamedList)) fail("Response has a top-level 'facets' property, but it is not a NamedList");
+
+    return (NamedList<Object>) o;
+  }
+
+  private void assertFacetResponseHasFacetWithBuckets(NamedList<Object> facetResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+    Object o = facetResponse.get(expectedFacetName);
+    if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+    if (!(o instanceof NamedList)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a NamedList");
+
+    final NamedList<Object> expectedFacetTopLevel = (NamedList<Object>) o;
+    o = expectedFacetTopLevel.get("buckets");
+    if (o == null) fail("Response has no 'buckets' property under 'facets'");
+    if (!(o instanceof List)) fail("Response has no 'buckets' property containing actual facet information.");
+
+    final List<NamedList> bucketList = (List<NamedList>) o;
+    assertEquals("Expected " + expectedBuckets.length + " buckets, but found " + bucketList.size(),
+        expectedBuckets.length, bucketList.size());
+    for (int i = 0; i < expectedBuckets.length; i++) {
+      final FacetBucket expectedBucket = expectedBuckets[i];
+      final NamedList<Object> actualBucket = bucketList.get(i);
+      assertEquals(expectedBucket.getVal(), actualBucket.get("val"));
+      assertEquals(expectedBucket.getCount(), actualBucket.get("count"));
+    }
+  }
+
+  private void assertFacetResponseHasStatFacetWithValue(NamedList<Object> facetResponse, String expectedFacetName, Double expectedStatValue) {
+    Object o = facetResponse.get(expectedFacetName);
+    if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+    if (!(o instanceof Number)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a Number");
+
+    final Number actualStatValueAsNumber = (Number) o;
+    final Double actualStatValueAsDouble = ((Number) o).doubleValue();
+    assertEquals(expectedStatValue, actualStatValueAsDouble, 0.5);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
new file mode 100644
index 0000000..d437d14
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+public class DomainMapTest extends SolrTestCaseJ4 {
+
+  @Test
+  public void testRejectsInvalidFilters() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .withFilter(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresFilterWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .withFilter("name:Solr");
+    final List<String> filterList = (List<String>) domain.get("filter");
+
+    assertTrue("Expected filter list to contain provided filter", filterList.contains("name:Solr"));
+  }
+
+  @Test
+  public void testStoresMultipleFilters() {
+    final DomainMap domain = new DomainMap()
+        .withFilter("name:Solr")
+        .withFilter("cat:search");
+    final List<String> filterList = (List<String>) domain.get("filter");
+
+    assertTrue("Expected filter list to contain 1st provided filter", filterList.contains("name:Solr"));
+    assertTrue("Expected filter list to contain 2nd provided filter", filterList.contains("cat:search"));
+  }
+
+  @Test
+  public void testRejectsInvalidQueries() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .withQuery(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresQueryWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .withQuery("name:Solr");
+    final List<String> queryList = (List<String>) domain.get("query");
+
+    assertTrue("Expected query list to contain provided query", queryList.contains("name:Solr"));
+  }
+
+  @Test
+  public void testStoresMultipleQueries() {
+    final DomainMap domain = new DomainMap()
+        .withQuery("name:Solr")
+        .withQuery("cat:search");
+    final List<String> queryList = (List<String>) domain.get("query");
+
+    assertTrue("Expected query list to contain 1st provided query", queryList.contains("name:Solr"));
+    assertTrue("Expected query list to contain 2nd provided query", queryList.contains("cat:search"));
+  }
+
+  @Test
+  public void testRejectsInvalidTagsToExclude() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .withTagsToExclude(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresTagsToExcludeWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .withTagsToExclude("BRAND");
+    final List<String> exclusionList = (List<String>) domain.get("excludeTags");
+
+    assertTrue("Expected tag-exclusion list to contain provided tag", exclusionList.contains("BRAND"));
+  }
+
+  @Test
+  public void testStoresMultipleTagExclusionStrings() {
+    final DomainMap domain = new DomainMap()
+        .withTagsToExclude("BRAND")
+        .withTagsToExclude("COLOR");
+    final List<String> exclusionList = (List<String>) domain.get("excludeTags");
+
+    assertTrue("Expected tag-exclusion list to contain provided 1st tag", exclusionList.contains("BRAND"));
+    assertTrue("Expected tag-exclusion list to contain provided 2nd tag", exclusionList.contains("COLOR"));
+  }
+
+  @Test
+  public void testRejectsInvalidBlockParentQuery() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .setBlockParentQuery(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresBlockParentQueryWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .setBlockParentQuery("content_type:product");
+    assertEquals("content_type:product", domain.get("blockParent"));
+  }
+
+  @Test
+  public void testRejectsInvalidBlockChildrenQuery() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .setBlockChildQuery(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresBlockChildrenQueryWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .setBlockChildQuery("content_type:productColors");
+    assertEquals("content_type:productColors", domain.get("blockChildren"));
+  }
+
+  @Test
+  public void testRejectsInvalidJoinFromParam() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .setJoinTransformation(null, "valid-to-field");
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testRejectsInvalidJoinToParam() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new DomainMap()
+          .setJoinTransformation("valid-from-field", null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresJoinValuesWithCorrectKey() {
+    final DomainMap domain = new DomainMap()
+        .setJoinTransformation("any-from-field", "any-to-field");
+
+    assertTrue(domain.containsKey("join"));
+    final Map<String, Object> joinParams = (Map<String, Object>) domain.get("join");
+    assertEquals("any-from-field", joinParams.get("from"));
+    assertEquals("any-to-field", joinParams.get("to"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
new file mode 100644
index 0000000..9063714
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+
+public class HeatmapFacetMapTest extends SolrTestCaseJ4 {
+
+  @Test
+  public void testRejectsInvalidFieldName() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresFieldNameWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME");
+    assertEquals("ANY_FIELD_NAME", heatmapFacet.get("field"));
+  }
+
+  @Test
+  public void testDoesntSupportSubfacets() {
+    final Throwable thrown = expectThrows(UnsupportedOperationException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .withSubFacet("ANY_NAME", new TermsFacetMap("ANY_OTHER_FIELD_NAME"));
+    });
+    assertThat(thrown.getMessage(), containsString("doesn't currently support subfacets"));
+  }
+
+  @Test
+  public void testRejectsInvalidRegionQueries() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .setRegionQuery(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresRegionQueryWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+        .setRegionQuery("[-120,-35 TO 50,60]");
+    assertEquals("[-120,-35 TO 50,60]", heatmapFacet.get("geom"));
+  }
+
+  @Test
+  public void testRejectsInvalidCellSize() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .setGridLevel(0);
+    });
+    assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+  }
+
+  @Test
+  public void testStoresCellSizeWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+        .setGridLevel(42);
+    assertEquals(42, heatmapFacet.get("gridLevel"));
+  }
+
+  @Test
+  public void testRejectsInvalidDistanceError() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .setDistErr(-1.0);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-negative"));
+  }
+
+  @Test
+  public void testStoresDistanceErrorWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+        .setDistErr(4.5);
+    assertEquals(4.5, heatmapFacet.get("distErr"));
+  }
+
+  @Test
+  public void testRejectsInvalidDistanceErrorPercentageWithCorrectKey() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .setDistErrPct(2.0);
+    });
+    assertThat(thrown.getMessage(), containsString("must be between 0.0 and 1.0"));
+  }
+
+  @Test
+  public void testStoresDistanceErrorPercentageWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+        .setDistErrPct(0.45);
+    assertEquals(0.45, heatmapFacet.get("distErrPct"));
+  }
+
+  @Test
+  public void testRejectsInvalidHeatmapFormat() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new HeatmapFacetMap("ANY_FIELD_NAME")
+          .setHeatmapFormat(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresHeatmapFormatWithCorrectKey() {
+    final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+        .setHeatmapFormat(HeatmapFacetMap.HeatmapFormat.PNG);
+    assertEquals("png", heatmapFacet.get("format"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
new file mode 100644
index 0000000..7717f7f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
@@ -0,0 +1,530 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.UpdateResponse;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.ExternalPaths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JsonQueryRequestFacetingIntegrationTest extends SolrCloudTestCase {
+
+  private static final String COLLECTION_NAME = "techproducts";
+  private static final String CONFIG_NAME = "techproducts_config";
+  private static final int NUM_TECHPRODUCTS_DOCS = 32;
+  private static final int NUM_IN_STOCK = 17;
+  private static final int NUM_ELECTRONICS = 12;
+  private static final int NUM_CURRENCY = 4;
+  private static final int NUM_MEMORY = 3;
+  private static final int NUM_CORSAIR = 3;
+  private static final int NUM_BELKIN = 2;
+  private static final int NUM_CANON = 2;
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(1)
+        .addConfig(CONFIG_NAME, new File(ExternalPaths.TECHPRODUCTS_CONFIGSET).toPath())
+        .configure();
+
+    final List<String> solrUrls = new ArrayList<>();
+    solrUrls.add(cluster.getJettySolrRunner(0).getBaseUrl().toString());
+
+    CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1).process(cluster.getSolrClient());
+
+    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    up.setParam("collection", COLLECTION_NAME);
+    up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
+    up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+    UpdateResponse updateResponse = up.process(cluster.getSolrClient());
+    assertEquals(0, updateResponse.getStatus());
+  }
+
+  @Test
+  public void testSingleTermsFacet() throws Exception {
+    final TermsFacetMap categoriesFacetMap = new TermsFacetMap("cat")
+        .setLimit(3);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_cats", categoriesFacetMap);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_cats",
+        new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY),
+        new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testFacetCanBeRepresentedByMapWriter() throws Exception {
+    final MapWriter categoriesFacet = new MapWriter() {
+      @Override
+      public void writeMap(EntryWriter ew) throws IOException {
+        ew.put("type", "terms");
+        ew.put("field", "cat");
+        ew.put("limit", 3);
+      }
+    };
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_cats", categoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_cats",
+        new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY),
+        new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testMultiTermsFacet() throws Exception {
+    final TermsFacetMap categoriesFacetMap = new TermsFacetMap("cat")
+        .setLimit(3);
+    final TermsFacetMap manufacturersFacetMap = new TermsFacetMap("manu_id_s")
+        .setLimit(3);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_cats", categoriesFacetMap)
+        .withFacet("top_manufacturers", manufacturersFacetMap);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_cats",
+        new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY),
+        new FacetBucket("memory", NUM_MEMORY));
+    assertHasFacetWithBucketValues(rawResponse,"top_manufacturers",
+        new FacetBucket("corsair",NUM_CORSAIR),
+        new FacetBucket("belkin", NUM_BELKIN),
+        new FacetBucket("canon", NUM_CANON));
+  }
+
+  @Test
+  public void testSingleRangeFacet() throws Exception {
+    final RangeFacetMap pricesFacet = new RangeFacetMap("price", 0, 100, 20);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("prices", pricesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"prices",
+        new FacetBucket(0.0f, 5),
+        new FacetBucket(20.0f, 0),
+        new FacetBucket(40.0f, 0),
+        new FacetBucket(60.0f, 1),
+        new FacetBucket(80.0f, 1));
+  }
+
+  @Test
+  public void testMultiRangeFacet() throws Exception {
+    final RangeFacetMap pricesFacet = new RangeFacetMap("price", 0, 100, 20);
+    final RangeFacetMap shippingWeightFacet = new RangeFacetMap("weight", 0, 200, 50);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("prices", pricesFacet)
+        .withFacet("shipping_weights", shippingWeightFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"prices",
+        new FacetBucket(0.0f, 5),
+        new FacetBucket(20.0f, 0),
+        new FacetBucket(40.0f, 0),
+        new FacetBucket(60.0f, 1),
+        new FacetBucket(80.0f, 1));
+    assertHasFacetWithBucketValues(rawResponse, "shipping_weights",
+        new FacetBucket(0.0f, 6),
+        new FacetBucket(50.0f, 0),
+        new FacetBucket(100.0f, 0),
+        new FacetBucket(150.0f,1));
+  }
+
+  @Test
+  public void testSingleStatFacet() throws Exception {
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withStatFacet("sum_price", "sum(price)");
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+  }
+
+  @Test
+  public void testMultiStatFacet() throws Exception {
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withStatFacet("sum_price", "sum(price)")
+        .withStatFacet("avg_price", "avg(price)");
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+    assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+  }
+
+  @Test
+  public void testMultiFacetsMixedTypes() throws Exception {
+    final TermsFacetMap categoryFacet = new TermsFacetMap("cat")
+        .setLimit(3);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withStatFacet("avg_price", "avg(price)")
+        .withFacet("top_cats", categoryFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testNestedTermsFacet() throws Exception {
+    final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+        .setLimit(3)
+        .withSubFacet("top_manufacturers_for_cat", new TermsFacetMap("manu_id_s").setLimit(1));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_cats", categoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+    // Test subfacet values for each top-level facet bucket
+    final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+    final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+    assertFacetResponseHasFacetWithBuckets(electronicsSubFacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+    final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+    assertFacetResponseHasFacetWithBuckets(currencySubfacet, "top_manufacturers_for_cat", new FacetBucket("boa", 1));
+    final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+    assertFacetResponseHasFacetWithBuckets(memorySubfacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+  }
+
+  @Test
+  public void testNestedFacetsOfMixedTypes() throws Exception {
+    final String subfacetName = "avg_price_for_cat";
+
+    final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+        .setLimit(3)
+        .withStatSubFacet(subfacetName, "avg(price)");
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_cats", categoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    // Test top level facets
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+    // Test subfacet values for each top-level facet bucket
+    final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+    final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+    assertFacetResponseHasStatFacetWithValue(electronicsSubFacet, subfacetName, 252.02909261530095);
+    final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+    assertFacetResponseHasStatFacetWithValue(currencySubfacet, subfacetName, 0.0);
+    final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+    assertFacetResponseHasStatFacetWithValue(memorySubfacet, subfacetName, 129.99499893188477);
+  }
+
+  @Test
+  public void testFacetWithDomainFilteredBySimpleQueryString() throws Exception {
+    final TermsFacetMap popularCategoriesFacet = new TermsFacetMap("cat")
+        .setLimit(3)
+        .withDomain(new DomainMap()
+            .withFilter("popularity:[5 TO 10]"));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_popular_cats", popularCategoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+        new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+  }
+
+  @Test
+  public void testFacetWithDomainFilteredByLocalParamsQueryString() throws Exception {
+    final TermsFacetMap popularCategoriesFacet = new TermsFacetMap("cat")
+        .setLimit(3)
+        .withDomain(new DomainMap()
+            .withFilter("{!lucene df=\"popularity\" v=\"[5 TO 10]\"}"));
+
+    JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("top_popular_cats", popularCategoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+        new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+  }
+
+  @Test
+  public void testFacetWithArbitraryDomainFromQueryString() throws Exception {
+    final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+        .setLimit(3)
+        .withDomain(new DomainMap()
+            .withQuery("*:*"));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("cat:electronics")
+        .withFacet("top_cats", categoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+        new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+  }
+
+  @Test
+  public void testFacetWithArbitraryDomainFromLocalParamsQuery() throws Exception {
+    final TermsFacetMap searchCategoriesFacet = new TermsFacetMap("cat")
+        .withDomain(new DomainMap()
+            .withQuery("{!lucene df=\"cat\" v=\"search\"}"));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("cat:electronics")
+        .withFacet("largest_search_cats", searchCategoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"largest_search_cats",
+        new FacetBucket("search",2),
+        new FacetBucket("software", 2));
+  }
+
+  public void testFacetWithMultipleSimpleQueryClausesInArbitraryDomain() throws Exception {
+    final TermsFacetMap solrCategoriesFacet = new TermsFacetMap("cat")
+        .withDomain(new DomainMap()
+            .withQuery("cat:search")
+            .withQuery("name:Solr"));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("cat:electronics")
+        .withFacet("cats_matching_solr", solrCategoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+        new FacetBucket("search",1),
+        new FacetBucket("software", 1));
+  }
+
+  public void testFacetWithMultipleLocalParamsQueryClausesInArbitraryDomain() throws Exception {
+    final TermsFacetMap solrCategoriesFacet = new TermsFacetMap("cat")
+        .withDomain(new DomainMap()
+            .withQuery("{!lucene df=\"cat\" v=\"search\"}")
+            .withQuery("{!lucene df=\"name\" v=\"Solr\"}"));
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("cat:electronics")
+        .withFacet("cats_matching_solr", solrCategoriesFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+        new FacetBucket("search",1),
+        new FacetBucket("software", 1));
+  }
+
+  @Test
+  public void testFacetWithDomainWidenedUsingExcludeTagsToIgnoreFilters() throws Exception {
+    final TermsFacetMap inStockFacet = new TermsFacetMap("cat")
+        .setLimit(2);
+    final TermsFacetMap allProductsFacet = new TermsFacetMap("cat")
+        .setLimit(2).withDomain(new DomainMap().withTagsToExclude("on_shelf"));
+    final Map<String, Object> taggedFilterMap = new HashMap<>();
+    taggedFilterMap.put("#on_shelf", "inStock:true");
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFilter(taggedFilterMap)
+        .withFacet("in_stock_only", inStockFacet)
+        .withFacet("all", allProductsFacet);
+
+    QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+    assertEquals(0, response.getStatus());
+    final SolrDocumentList returnedDocs = response.getResults();
+    assertEquals(NUM_IN_STOCK, returnedDocs.getNumFound());
+    assertEquals(10, returnedDocs.size());
+    final NamedList<Object> rawResponse = response.getResponse();
+    assertHasFacetWithBucketValues(rawResponse,"in_stock_only",
+        new FacetBucket("electronics",8),
+        new FacetBucket("currency", 4));
+    assertHasFacetWithBucketValues(rawResponse,"all",
+        new FacetBucket("electronics",12),
+        new FacetBucket("currency", 4));
+  }
+
+  private class FacetBucket {
+    private final Object val;
+    private final int count;
+    FacetBucket(Object val, int count) {
+      this.val = val;
+      this.count = count;
+    }
+
+    public Object getVal() { return val; }
+    public int getCount() { return count; }
+  }
+
+  private void assertHasFacetWithBucketValues(NamedList<Object> rawResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+    final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+    assertFacetResponseHasFacetWithBuckets(facetsTopLevel, expectedFacetName, expectedBuckets);
+  }
+
+  private void assertHasStatFacetWithValue(NamedList<Object> rawResponse, String expectedFacetName, Double expectedStatValue) {
+    final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+    assertFacetResponseHasStatFacetWithValue(facetsTopLevel, expectedFacetName, expectedStatValue);
+  }
+
+  private NamedList<Object> assertHasFacetResponse(NamedList<Object> topLevelResponse) {
+    Object o = topLevelResponse.get("facets");
+    if (o == null) fail("Response has no top-level 'facets' property as expected");
+    if (!(o instanceof NamedList)) fail("Response has a top-level 'facets' property, but it is not a NamedList");
+
+    return (NamedList<Object>) o;
+  }
+
+  private void assertFacetResponseHasFacetWithBuckets(NamedList<Object> facetResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+    Object o = facetResponse.get(expectedFacetName);
+    if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+    if (!(o instanceof NamedList)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a NamedList");
+
+    final NamedList<Object> expectedFacetTopLevel = (NamedList<Object>) o;
+    o = expectedFacetTopLevel.get("buckets");
+    if (o == null) fail("Response has no 'buckets' property under 'facets'");
+    if (!(o instanceof List)) fail("Response has no 'buckets' property containing actual facet information.");
+
+    final List<NamedList> bucketList = (List<NamedList>) o;
+    assertEquals("Expected " + expectedBuckets.length + " buckets, but found " + bucketList.size(),
+        expectedBuckets.length, bucketList.size());
+    for (int i = 0; i < expectedBuckets.length; i++) {
+      final FacetBucket expectedBucket = expectedBuckets[i];
+      final NamedList<Object> actualBucket = bucketList.get(i);
+      assertEquals(expectedBucket.getVal(), actualBucket.get("val"));
+      assertEquals(expectedBucket.getCount(), actualBucket.get("count"));
+    }
+  }
+
+  private void assertFacetResponseHasStatFacetWithValue(NamedList<Object> facetResponse, String expectedFacetName, Double expectedStatValue) {
+    Object o = facetResponse.get(expectedFacetName);
+    if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+    if (!(o instanceof Number)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a Number");
+
+    final Number actualStatValueAsNumber = (Number) o;
+    final Double actualStatValueAsDouble = ((Number) o).doubleValue();
+    assertEquals(expectedStatValue, actualStatValueAsDouble, 0.5);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
index 84ef956..6e8c647 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
@@ -25,7 +25,6 @@ import java.util.Map;
 
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
 import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.MapWriter;
 import org.junit.Test;
@@ -98,6 +97,91 @@ public class JsonQueryRequestUnitTest extends LuceneTestCase {
   }
 
   @Test
+  public void testRejectsInvalidFacetName() {
+    Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new JsonQueryRequest().withFacet(null, new HashMap<>());
+    });
+    assertThat(thrown.getMessage(),containsString("must be non-null"));
+
+    thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new JsonQueryRequest().withStatFacet(null, "avg(price)");
+    });
+    assertThat(thrown.getMessage(),containsString("must be non-null"));
+  }
+
+  @Test
+  public void testRejectsInvalidFacetMap() {
+    Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new JsonQueryRequest().withFacet("anyFacetName", (Map<String, Object>)null);
+    });
+    assertThat(thrown.getMessage(),containsString("must be non-null"));
+  }
+
+  @Test
+  public void testRejectsNullFacetMapWriter() {
+    Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new JsonQueryRequest().withFacet("anyFacetName", (MapWriter)null);
+    });
+    assertThat(thrown.getMessage(),containsString("must be non-null"));
+  }
+
+  @Test
+  public void testRejectsInvalidStatFacetString() {
+    Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new JsonQueryRequest().withStatFacet("anyFacetName", (String)null);
+    });
+    assertThat(thrown.getMessage(),containsString("must be non-null"));
+  }
+
+  @Test
+  public void testWritesProvidedFacetMapToJsonCorrectly() {
+    final Map<String, Object> categoryFacetMap = new HashMap<>();
+    categoryFacetMap.put("type", "terms");
+    categoryFacetMap.put("field", "category");
+    final JsonQueryRequest request = new JsonQueryRequest().withFacet("top_categories", categoryFacetMap);
+    final String requestBody = writeRequestToJson(request);
+    assertThat(requestBody, containsString("\"facet\":{\"top_categories\":{\"field\":\"category\",\"type\":\"terms\"}}"));
+  }
+
+  @Test
+  public void testWritesProvidedFacetMapWriterToJsonCorrectly() {
+    final MapWriter facetWriter = new MapWriter() {
+      @Override
+      public void writeMap(EntryWriter ew) throws IOException {
+        ew.put("type", "terms");
+        ew.put("field", "category");
+      }
+    };
+    final JsonQueryRequest request = new JsonQueryRequest().withFacet("top_categories", facetWriter);
+    final String requestBody = writeRequestToJson(request);
+    assertThat(requestBody, containsString("\"facet\":{\"top_categories\":{\"type\":\"terms\",\"field\":\"category\"}}"));
+  }
+
+  @Test
+  public void testWritesProvidedStatFacetToJsonCorrectly() {
+    final JsonQueryRequest request = new JsonQueryRequest().withStatFacet("avg_price", "avg(price)");
+    final String requestBody = writeRequestToJson(request);
+    assertThat(requestBody, containsString("\"facet\":{\"avg_price\":\"avg(price)\"}"));
+  }
+
+  @Test
+  public void testWritesMultipleFacetMapsToJsonCorrectly() {
+    final Map<String, Object> facetMap1 = new HashMap<>();
+    facetMap1.put("type", "terms");
+    facetMap1.put("field", "a");
+    final Map<String, Object> facetMap2 = new HashMap<>();
+    facetMap2.put("type", "terms");
+    facetMap2.put("field", "b");
+    final JsonQueryRequest request = new JsonQueryRequest();
+
+    request.withFacet("facet1", facetMap1);
+    request.withFacet("facet2", facetMap2);
+    final String requestBody = writeRequestToJson(request);
+
+    assertThat(requestBody, containsString("\"facet\":{\"facet2\":{\"field\":\"b\",\"type\":\"terms\"},\"facet1\":{\"field\":\"a\",\"type\":\"terms\"}}"));
+  }
+
+  @Test
   public void testRejectsInvalidLimit() {
     Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
       new JsonQueryRequest().setLimit(-1);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
new file mode 100644
index 0000000..8d07a4f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+public class QueryFacetMapTest extends SolrTestCaseJ4 {
+  @Test
+  public void testSetsFacetTypeToQuery() {
+    final QueryFacetMap queryFacet = new QueryFacetMap("any:query");
+    assertEquals("query", queryFacet.get("type"));
+  }
+
+  @Test
+  public void testRejectsInvalidQueryString() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final QueryFacetMap queryFacet = new QueryFacetMap(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testSetsQueryWithCorrectKey() {
+    final QueryFacetMap queryFacet = new QueryFacetMap("any:query");
+    assertEquals("any:query", queryFacet.get("q"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
new file mode 100644
index 0000000..43bb5f5
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+/**
+ * Unit tests for {@link RangeFacetMap}
+ */
+public class RangeFacetMapTest extends SolrTestCaseJ4 {
+  @Test
+  public void testRejectsInvalidFieldName() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new RangeFacetMap(null, 1, 2, 3);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testRejectsInvalidStartEndBounds() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new RangeFacetMap("ANY_FIELD_NAME", 1, -1, 3);
+    });
+    assertThat(thrown.getMessage(), containsString("'end' must be greater than parameter 'start'"));
+  }
+
+  @Test
+  public void testRejectsInvalidGap() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new RangeFacetMap("ANY_FIELD_NAME", 1, 2, -1);
+    });
+    assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+  }
+
+  @Test
+  public void testStoresRequiredValuesWithCorrectKeys() {
+    final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3);
+    assertEquals("ANY_FIELD_NAME", rangeFacet.get("field"));
+    assertEquals(1L, rangeFacet.get("start"));
+    assertEquals(2L, rangeFacet.get("end"));
+    assertEquals(3L, rangeFacet.get("gap"));
+  }
+
+  @Test
+  public void testStoresHardEndWithCorrectKey() {
+    final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+        .setHardEnd(true);
+    assertEquals(true, rangeFacet.get("hardend"));
+  }
+
+  @Test
+  public void testRejectsInvalidOtherBuckets() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+          .setOtherBuckets(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresOtherBucketsValueWithCorrectKey() {
+    final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+        .setOtherBuckets(RangeFacetMap.OtherBuckets.BETWEEN);
+    assertEquals("between", rangeFacet.get("other"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
new file mode 100644
index 0000000..58f807d
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+import static org.junit.internal.matchers.StringContains.containsString;
+
+
+public class TermsFacetMapTest extends SolrTestCaseJ4 {
+  private static final String ANY_FIELD_NAME = "ANY_FIELD_NAME";
+
+  @Test
+  public void testSetsFacetTypeToTerm() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME);
+    assertEquals("terms", termsFacet.get("type"));
+  }
+
+  @Test
+  public void testStoresFieldWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME);
+    assertEquals(ANY_FIELD_NAME, termsFacet.get("field"));
+  }
+
+  @Test
+  public void testRejectsNegativeBucketOffset() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setBucketOffset(-1);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-negative"));
+  }
+
+  @Test
+  public void testStoresBucketOffsetWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setBucketOffset(2);
+    assertEquals(2, termsFacet.get("offset"));
+
+  }
+
+  @Test
+  public void testRejectsNegativeBucketLimit() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setLimit(-1);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-negative"));
+  }
+
+  @Test
+  public void testStoresBucketLimitWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setLimit(3);
+    assertEquals(3, termsFacet.get("limit"));
+  }
+
+  @Test
+  public void testRejectsInvalidSortString() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setSort(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresSortWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setSort("price asc");
+    assertEquals("price asc", termsFacet.get("sort"));
+  }
+
+  @Test
+  public void testRejectInvalidOverRequestBuckets() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setOverRequest(-2);
+    });
+    assertThat(thrown.getMessage(), containsString("must be >= -1"));
+  }
+
+  @Test
+  public void testStoresOverRequestBucketsWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setOverRequest(4);
+    assertEquals(4, termsFacet.get("overrequest"));
+  }
+
+  @Test
+  public void testStoresRefinementFlagWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .useDistributedFacetRefining(true);
+    assertEquals(true, termsFacet.get("refine"));
+  }
+
+  @Test
+  public void testRejectInvalidOverRefineBuckets() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setOverRefine(-2);
+    });
+    assertThat(thrown.getMessage(), containsString("must be >= -1"));
+  }
+
+  @Test
+  public void testStoresOverRefineBucketsWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setOverRefine(5);
+    assertEquals(5, termsFacet.get("overrefine"));
+  }
+
+  @Test
+  public void testRejectInvalidMinCount() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setMinCount(0);
+    });
+    assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+  }
+
+  @Test
+  public void testStoresMinCountWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setMinCount(6);
+    assertEquals(6, termsFacet.get("mincount"));
+  }
+
+  @Test
+  public void testStoresNumBucketsFlagWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .includeTotalNumBuckets(true);
+    assertEquals(true, termsFacet.get("numBuckets"));
+  }
+
+  @Test
+  public void testStoresAllBucketsFlagWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .includeAllBucketsUnionBucket(true);
+    assertEquals(true, termsFacet.get("allBuckets"));
+  }
+
+  @Test
+  public void testRejectInvalidTermPrefix() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setTermPrefix(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresTermPrefixWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setTermPrefix("ANY_PREF");
+    assertEquals("ANY_PREF", termsFacet.get("prefix"));
+  }
+
+  @Test
+  public void testRejectsInvalidMethod() {
+    final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+      final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+          .setFacetMethod(null);
+    });
+    assertThat(thrown.getMessage(), containsString("must be non-null"));
+  }
+
+  @Test
+  public void testStoresMethodWithCorrectKey() {
+    final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+        .setFacetMethod(TermsFacetMap.FacetMethod.STREAM);
+    assertEquals("stream", termsFacet.get("method"));
+  }
+}


Mime
View raw message