lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gerlowsk...@apache.org
Subject [2/2] lucene-solr:branch_7x: SOLR-12965: Add facet support to JsonQueryRequest
Date Wed, 14 Nov 2018 20:28:20 GMT
SOLR-12965: Add facet support to JsonQueryRequest


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/b502ba28
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/b502ba28
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/b502ba28

Branch: refs/heads/branch_7x
Commit: b502ba2882a86958ef8769c3cb2fd65cf2d9c7e1
Parents: 6faddfe
Author: Jason Gerlowski <gerlowskija@apache.org>
Authored: Sat Nov 10 19:48:50 2018 -0500
Committer: Jason Gerlowski <gerlowskija@apache.org>
Committed: Wed Nov 14 15:19:06 2018 -0500

----------------------------------------------------------------------
 solr/solr-ref-guide/src/json-facet-api.adoc     |  36 ++
 .../client/solrj/request/json/DomainMap.java    | 139 +++++
 .../solrj/request/json/HeatmapFacetMap.java     | 137 +++++
 .../client/solrj/request/json/JsonFacetMap.java |  62 ++
 .../solrj/request/json/JsonQueryRequest.java    | 109 ++++
 .../solrj/request/json/QueryFacetMap.java       |  39 ++
 .../solrj/request/json/RangeFacetMap.java       | 105 ++++
 .../solrj/request/json/TermsFacetMap.java       | 204 ++++++
 .../solrj/src/test-files/solrj/techproducts.xml | 421 +++++++++++++
 .../ref_guide_examples/JsonRequestApiTest.java  |  98 ++-
 ...JsonQueryRequestFacetingIntegrationTest.java | 615 +++++++++++++++++++
 .../solrj/request/json/DomainMapTest.java       | 177 ++++++
 .../solrj/request/json/HeatmapFacetMapTest.java | 130 ++++
 ...JsonQueryRequestFacetingIntegrationTest.java | 530 ++++++++++++++++
 .../request/json/JsonQueryRequestUnitTest.java  |  86 ++-
 .../solrj/request/json/QueryFacetMapTest.java   |  45 ++
 .../solrj/request/json/RangeFacetMapTest.java   |  84 +++
 .../solrj/request/json/TermsFacetMapTest.java   | 189 ++++++
 18 files changed, 3202 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solr-ref-guide/src/json-facet-api.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/json-facet-api.adoc b/solr/solr-ref-guide/src/json-facet-api.adoc
index d842517..fd40538 100644
--- a/solr/solr-ref-guide/src/json-facet-api.adoc
+++ b/solr/solr-ref-guide/src/json-facet-api.adoc
@@ -1,5 +1,7 @@
 = JSON Facet API
 :page-tocclass: right
+:solr-root-path: ../../
+:example-source-dir: {solr-root-path}solrj/src/test/org/apache/solr/client/ref_guide_examples/
 // 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
@@ -74,6 +76,11 @@ The response to the facet request above will start with documents matching the r
 
 Here's an example of a bucketing facet, that partitions documents into bucket based on the `cat` field (short for category), and returns the top 3 buckets:
 
+[.dynamic-tabs]
+--
+[example.tab-pane#curljsonsimpletermsfacet]
+====
+[.tab-label]*curl*
 [source,bash]
 ----
 curl http://localhost:8983/solr/techproducts/query -d 'q=*:*&
@@ -85,6 +92,18 @@ json.facet={
   }
 }'
 ----
+====
+
+[example.tab-pane#solrjjsonsimpletermsfacet]
+====
+[.tab-label]*SolrJ*
+
+[source,java,indent=0]
+----
+include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-simple-terms-facet]
+----
+====
+--
 
 The response below shows us that 32 documents match the default root domain. and 12 documents have `cat:electronics`, 4 documents have `cat:currency`, etc.
 
@@ -132,6 +151,11 @@ curl http://localhost:8983/solr/techproducts/query -d 'q=*:*&json.facet=
 
 Another option is to use the JSON Request API to provide the entire request in JSON:
 
+[.dynamic-tabs]
+--
+[example.tab-pane#curljsontermsfacet2]
+====
+[.tab-label]*curl*
 [source,bash]
 ----
 curl http://localhost:8983/solr/techproducts/query -d '
@@ -144,6 +168,18 @@ curl http://localhost:8983/solr/techproducts/query -d '
 }
 '
 ----
+====
+
+[example.tab-pane#solrjjsontermsfacet2]
+====
+[.tab-label]*SolrJ*
+
+[source,java,indent=0]
+----
+include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-terms-facet2]
+----
+====
+--
 
 === JSON Extensions
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
new file mode 100644
index 0000000..c23cee9
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
@@ -0,0 +1,139 @@
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DomainMap extends HashMap<String, Object> {
+
+  /**
+   * Indicates that the domain should be narrowed by the specified filter
+   *
+   * May be called multiple times.  Each added filter is retained and used to narrow the domain.
+   */
+  public DomainMap withFilter(String filter) {
+    if (filter == null) {
+      throw new IllegalArgumentException("Parameter 'filter' must be non-null");
+    }
+
+    if (! containsKey("filter")) {
+      put("filter", new ArrayList<String>());
+    }
+
+    final List<String> filterList = (List<String>) get("filter");
+    filterList.add(filter);
+    return this;
+  }
+
+  /**
+   * Indicates that the domain should be the following query
+   *
+   * May be called multiple times.  Each specified query is retained and included in the domain.
+   */
+  public DomainMap withQuery(String query) {
+    if (query == null) {
+      throw new IllegalArgumentException("Parameter 'query' must be non-null");
+    }
+
+    if (! containsKey("query")) {
+      put("query", new ArrayList<String>());
+    }
+
+    final List<String> queryList = (List<String>) get("query");
+    queryList.add(query);
+    return this;
+  }
+
+  /**
+   * Provide a tag or tags that correspond to filters or queries to exclude from the domain
+   *
+   * May be called multiple times.  Each exclude-string is retained and used for removing queries/filters from the
+   * domain specification.
+   *
+   * @param excludeTagsValue a comma-delimited String containing filter/query tags to exclude
+   */
+  public DomainMap withTagsToExclude(String excludeTagsValue) {
+    if (excludeTagsValue == null) {
+      throw new IllegalArgumentException("Parameter 'excludeTagValue' must be non-null");
+    }
+
+    if (! containsKey("excludeTags")) {
+      put("excludeTags", new ArrayList<String>());
+    }
+
+    final List<String> excludeTagsList = (List<String>) get("excludeTags");
+    excludeTagsList.add(excludeTagsValue);
+    return this;
+  }
+
+  /**
+   * Indicates that the resulting domain will contain all parent documents of the children in the existing domain
+   *
+   * @param allParentsQuery a query used to identify all parent documents in the collection
+   */
+  public DomainMap setBlockParentQuery(String allParentsQuery) {
+    if (allParentsQuery == null) {
+      throw new IllegalArgumentException("Parameter 'allParentsQuery' must be non-null");
+    }
+
+    put("blockParent", allParentsQuery);
+    return this;
+  }
+
+  /**
+   * Indicates that the resulting domain will contain all child documents of the parents in the current domain
+   *
+   * @param allChildrenQuery a query used to identify all child documents in the collection
+   */
+  public DomainMap setBlockChildQuery(String allChildrenQuery) {
+    if (allChildrenQuery == null) {
+      throw new IllegalArgumentException("Parameter 'allChildrenQuery' must be non-null");
+    }
+
+    put("blockChildren", allChildrenQuery);
+    return this;
+  }
+
+  /**
+   * Transforms the domain by running a join query with the provided {@code from} and {@code to} parameters
+   *
+   * Join modifies the current domain by selecting the documents whose values in field {@code to} match values for the
+   * field {@code from} in the current domain.
+   *
+   * @param from a field-name whose values are matched against {@code to} by the join
+   * @param to a field name whose values should match values specified by the {@code from} field
+   */
+  public DomainMap setJoinTransformation(String from, String to) {
+    if (from == null) {
+      throw new IllegalArgumentException("Parameter 'from' must be non-null");
+    }
+    if (to == null) {
+      throw new IllegalArgumentException("Parameter 'to' must be non-null");
+    }
+
+    final Map<String, Object> joinParameters = new HashMap<>();
+    joinParameters.put("from", from);
+    joinParameters.put("to", to);
+    put("join", joinParameters);
+
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
new file mode 100644
index 0000000..ed64e08
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
@@ -0,0 +1,137 @@
+/*
+ * 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.Map;
+
+/**
+ * Represents a "heatmap" facet in a JSON request query.
+ *
+ * Ready for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class HeatmapFacetMap extends JsonFacetMap<HeatmapFacetMap> {
+  public HeatmapFacetMap(String fieldName) {
+    super("heatmap");
+
+    if (fieldName == null) {
+      throw new IllegalArgumentException("Parameter 'fieldName' must be non-null");
+    }
+
+    put("field", fieldName);
+  }
+
+  @Override
+  public HeatmapFacetMap getThis() { return this; }
+
+  @Override
+  public HeatmapFacetMap withSubFacet(String facetName, JsonFacetMap map) {
+    throw new UnsupportedOperationException(getClass().getName() + " doesn't currently support subfacets");
+  }
+
+  /**
+   * Indicate the region to compute the heatmap facet on.
+   *
+   * Defaults to the "world" ("[-180,-90 TO 180,90]")
+   */
+  public HeatmapFacetMap setRegionQuery(String queryString) {
+    if (queryString == null) {
+      throw new IllegalArgumentException("Parameter 'queryString' must be non-null");
+    }
+
+    put("geom", queryString);
+    return this;
+  }
+
+  /**
+   * Indicates the size of each cell in the computed heatmap grid
+   *
+   * If not set, defaults to being computed by {@code distErrPct} or {@code distErr}
+   *
+   * @param individualCellSize the forced size of each cell in the heatmap grid
+   *
+   * @see #setDistErr(double)
+   * @see #setDistErrPct(double)
+   */
+  public HeatmapFacetMap setGridLevel(int individualCellSize) {
+    if (individualCellSize <= 0) {
+      throw new IllegalArgumentException("Parameter 'individualCellSize' must be a positive integer");
+    }
+    put("gridLevel", individualCellSize);
+    return this;
+  }
+
+  /**
+   * A fraction of the heatmap region that is used to compute the cell size.
+   *
+   * Defaults to 0.15 if not specified.
+   *
+   * @see #setGridLevel(int)
+   * @see #setDistErr(double)
+   */
+  public HeatmapFacetMap setDistErrPct(double distErrPct) {
+    if (distErrPct < 0 || distErrPct > 1) {
+      throw new IllegalArgumentException("Parameter 'distErrPct' must be between 0.0 and 1.0");
+    }
+    put("distErrPct", distErrPct);
+    return this;
+  }
+
+  /**
+   * Indicates the maximum acceptable cell error distance.
+   *
+   * Used to compute the size of each cell in the heatmap grid rather than specifying {@link #setGridLevel(int)}
+   *
+   * @param distErr a positive value representing the maximum acceptable cell error.
+   *
+   * @see #setGridLevel(int)
+   * @see #setDistErrPct(double)
+   */
+  public HeatmapFacetMap setDistErr(double distErr) {
+    if (distErr < 0) {
+      throw new IllegalArgumentException("Parameter 'distErr' must be non-negative");
+    }
+    put("distErr", distErr);
+    return this;
+  }
+
+  public enum HeatmapFormat {
+    INTS2D("ints2D"), PNG("png");
+
+    private final String value;
+
+    HeatmapFormat(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public String toString() { return value; }
+  }
+
+  /**
+   * Sets the format that the computed heatmap should be returned in.
+   *
+   * Defaults to 'ints2D' if not specified.
+   */
+  public HeatmapFacetMap setHeatmapFormat(HeatmapFormat format) {
+    if (format == null) {
+      throw new IllegalArgumentException("Parameter 'format' must be non-null");
+    }
+    put("format", format.toString());
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
new file mode 100644
index 0000000..3d3e6de
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
@@ -0,0 +1,62 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+/**
+ * A common parent for a small set of classes that allow easier composition of JSON facet objects.
+ *
+ * Designed for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public abstract class JsonFacetMap<B extends JsonFacetMap<B>> extends HashMap<String, Object> {
+
+  public abstract B getThis(); // Allows methods shared here to return subclass type
+
+  public JsonFacetMap(String facetType) {
+    super();
+
+    put("type", facetType);
+  }
+
+  public B withDomain(DomainMap domain) {
+    put("domain", domain);
+    return getThis();
+  }
+
+  public B withSubFacet(String facetName, JsonFacetMap map) {
+    if (! containsKey("facet")) {
+      put("facet", new HashMap<String, Object>());
+    }
+
+    final Map<String, Object> subFacetMap = (Map<String, Object>) get("facet");
+    subFacetMap.put(facetName, map);
+    return getThis();
+  }
+
+  public B withStatSubFacet(String facetName, String statFacet) {
+    if (! containsKey("facet")) {
+      put("facet", new HashMap<String, Object>());
+    }
+
+    final Map<String, Object> subFacetMap = (Map<String, Object>) get("facet");
+    subFacetMap.put(facetName, statFacet);
+    return getThis();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
index 781d9c3..1c7b071 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
@@ -133,6 +133,115 @@ public class JsonQueryRequest extends QueryRequest {
   }
 
   /**
+   * Specify a facet sent as a part of this JSON request.
+   *
+   * This method may be called multiple times.  Each call made with a different {@code facetName} value will add a new
+   * top-level facet.  Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+   * overwritten.
+   * <p>
+   * <b>Example:</b> You wish to send the JSON request: {"query": "*:*", "facet": { "top_cats":{"type": "terms", "field":"cat"}}}.  You
+   * would represent (and attach) the facet in this request as follows:
+   * <pre>{@code
+   *     final Map<String, Object> catFacetMap = new HashMap<>();
+   *     catFacetMap.put("type", "terms");
+   *     catFacetMap.put("field", "cat");
+   *
+   *     jsonQueryRequest.withStatFacet("top_cats", catFacetMap);
+   * }</pre>
+   *
+   * @param facetName the name of the top-level facet you'd like to add.
+   * @param facetJson a Map of values representing the facet you wish to add to the request
+   */
+  public JsonQueryRequest withFacet(String facetName, Map<String, Object> facetJson) {
+    if (facetName == null) {
+      throw new IllegalArgumentException("'facetName' parameter must be non-null");
+    }
+    if (facetJson == null) {
+      throw new IllegalArgumentException("'facetMap' parameter must be non-null");
+    }
+
+    if (! jsonRequestMap.containsKey("facet")) {
+      jsonRequestMap.put("facet", new HashMap<String, Object>());
+    }
+
+    final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+    facetMap.put(facetName, facetJson);
+    return this;
+  }
+
+  /**
+   * Specify a facet sent as a part of this JSON request.
+   *
+   * This method may be called multiple times.  Each call made with a different {@code facetName} value will add a new
+   * top-level facet.  Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+   * overwritten.
+   * <p>
+   * <b>Example:</b> You wish to send the JSON request: {"query": "*:*", "facet": { "top_cats":{"type": "terms", "field":"cat"}}}.  You
+   * would represent the facet in this request as follows:
+   * <pre>
+   *     final MapWriter facetWriter = new MapWriter() {
+   *         &#64;Override
+   *         public void writeMap(EntryWriter ew) throws IOException {
+   *             ew.put("type", "terms");
+   *             ew.put("field", "cat");
+   *         }
+   *     };
+   * </pre>
+   *
+   * @param facetName the name of the top-level facet you'd like to add.
+   * @param facetWriter a MapWriter representing the facet you wish to add to the request
+   */
+  public JsonQueryRequest withFacet(String facetName, MapWriter facetWriter) {
+    if (facetName == null) {
+      throw new IllegalArgumentException("'facetName' parameter must be non-null");
+    }
+    if (facetWriter == null) {
+      throw new IllegalArgumentException("'facetWriter' parameter must be non-null");
+    }
+
+    if (! jsonRequestMap.containsKey("facet")) {
+      jsonRequestMap.put("facet", new HashMap<String, Object>());
+    }
+
+    final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+    facetMap.put(facetName, facetWriter);
+    return this;
+  }
+
+  /**
+   * Specify a simple stat or aggregation facet to be sent as a part of this JSON request.
+   *
+   * This method may be called multiple times.  Each call made with a different {@code facetName} value will add a new
+   * top-level facet.  Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+   * overwritten.
+   * <p>
+   * <b>Example:</b>  You wish to send the JSON request: {"query": "*:*", "facet": {"avg_price": "avg(price)"}}.  You
+   * would represent the facet in this request as follows:
+   * <pre>{@code
+   *     jsonQueryRequest.withStatFacet("avg_price", "avg(price)");
+   * }</pre>
+   *
+   * @param facetName the name of the top-level stat/agg facet you'd like to add.
+   * @param facetValue a String representing the stat/agg facet computation to perform.
+   */
+  public JsonQueryRequest withStatFacet(String facetName, String facetValue) {
+    if (facetName == null) {
+      throw new IllegalArgumentException("'facetName' parameter must be non-null");
+    }
+    if (facetValue == null) {
+      throw new IllegalArgumentException("'facetValue' parameter must be non-null");
+    }
+
+    if (! jsonRequestMap.containsKey("facet")) {
+      jsonRequestMap.put("facet", new HashMap<String, Object>());
+    }
+
+    final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+    facetMap.put(facetName, facetValue);
+    return this;
+  }
+
+  /**
    * Specify whether results should be fetched starting from a particular offset (or 'start').
    *
    * Defaults to 0 if not set.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
new file mode 100644
index 0000000..7613183
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
@@ -0,0 +1,39 @@
+/*
+ * 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.Map;
+
+/**
+ * Represents a "query" facet in a JSON query request.
+ *
+ * Ready for use in {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class QueryFacetMap extends JsonFacetMap<QueryFacetMap> {
+  public QueryFacetMap(String queryString) {
+    super("query");
+
+    if (queryString == null) {
+      throw new IllegalArgumentException("Parameter 'queryString' must be non-null");
+    }
+    put("q", queryString);
+  }
+
+  @Override
+  public QueryFacetMap getThis() { return this; }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
new file mode 100644
index 0000000..24d5123
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
@@ -0,0 +1,105 @@
+/*
+ * 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.Map;
+
+/**
+ * Represents a "range" facet in a JSON request query.
+ *
+ * Ready for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class RangeFacetMap extends JsonFacetMap<RangeFacetMap> {
+  public RangeFacetMap(String field, long start, long end, long gap) {
+    super("range");
+
+    if (field == null) {
+      throw new IllegalArgumentException("Parameter 'field' must be non-null");
+    }
+    if (end < start) {
+      throw new IllegalArgumentException("Parameter 'end' must be greater than parameter 'start'");
+    }
+    if (gap <= 0) {
+      throw new IllegalArgumentException("Parameter 'gap' must be a positive integer");
+    }
+
+    put("field", field);
+    put("start", start);
+    put("end", end);
+    put("gap", gap);
+  }
+
+  public RangeFacetMap(String field, double start, double end, double gap) {
+    super("range");
+
+    if (field == null) {
+      throw new IllegalArgumentException("Parameter 'field' must be non-null");
+    }
+    if (end < start) {
+      throw new IllegalArgumentException("Parameter 'end' must be greater than parameter 'start'");
+    }
+    if (gap <= 0) {
+      throw new IllegalArgumentException("Parameter 'gap' must be a positive value");
+    }
+
+    put("field", field);
+    put("start", start);
+    put("end", end);
+    put("gap", gap);
+  }
+
+  @Override
+  public RangeFacetMap getThis() { return this; }
+
+  /**
+   * Indicates whether the facet's last bucket should stop exactly at {@code end}, or be extended to be {@code gap} wide
+   *
+   * Defaults to false if not specified.
+   *
+   * @param hardEnd true if the final bucket should be truncated at {@code end}; false otherwise
+   */
+  public RangeFacetMap setHardEnd(boolean hardEnd) {
+    put("hardend", hardEnd);
+    return this;
+  }
+
+  public enum OtherBuckets {
+    BEFORE("before"), AFTER("after"), BETWEEN("between"), NONE("none"), ALL("all");
+
+    private final String value;
+
+    OtherBuckets(String value) {
+      this.value = value;
+    }
+
+    public String toString() { return value; }
+  }
+
+  /**
+   * Indicates that an additional range bucket(s) should be computed and added to those computed for {@code start} and {@code end}
+   *
+   * See {@link OtherBuckets} for possible options.
+   */
+  public RangeFacetMap setOtherBuckets(OtherBuckets bucketSpecifier) {
+    if (bucketSpecifier == null) {
+      throw new IllegalArgumentException("Parameter 'bucketSpecifier' must be non-null");
+    }
+    put("other", bucketSpecifier.toString());
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
new file mode 100644
index 0000000..e28f8a8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
@@ -0,0 +1,204 @@
+/*
+ * 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.Map;
+
+/**
+ * Represents a "terms" facet in a JSON query request.
+ *
+ * Ready for use in {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class TermsFacetMap extends JsonFacetMap<TermsFacetMap> {
+  public TermsFacetMap(String fieldName) {
+    super("terms");
+
+    put("field", fieldName);
+  }
+
+  @Override
+  public TermsFacetMap getThis() { return this; }
+
+  /**
+   * Indicates that Solr should skip over the N buckets for this facet.
+   *
+   * Used for "paging" in facet results.  Defaults to 0 if not provided.
+   *
+   * @param numToSkip the number of buckets to skip over before selecting the buckets to return
+   */
+  public TermsFacetMap setBucketOffset(int numToSkip) {
+    if (numToSkip < 0) {
+      throw new IllegalArgumentException("Parameter 'numToSkip' must be non-negative");
+    }
+    put("offset", numToSkip);
+    return this;
+  }
+
+  /**
+   * Indicates the maximum number of buckets to be returned by this facet.
+   *
+   * Defaults to 10 if not specified.
+   */
+  public TermsFacetMap setLimit(int maximumBuckets) {
+    if (maximumBuckets < 0) {
+      throw new IllegalArgumentException("Parameter 'maximumBuckets' must be non-negative");
+    }
+    put("limit", maximumBuckets);
+    return this;
+  }
+
+  /**
+   * Indicates the desired ordering for the returned buckets.
+   *
+   * Values can be based on 'count' (the number of results in each bucket), 'index' (the natural order of bucket values),
+   * or on any stat facet that occurs in the bucket.  Defaults to "count desc" if not specified.
+   */
+  public TermsFacetMap setSort(String sortString) {
+    if (sortString == null) {
+      throw new IllegalArgumentException("Parameter 'sortString' must be non-null");
+    }
+    put("sort", sortString);
+    return this;
+  }
+
+  /**
+   * Indicates the number of additional buckets to request internally beyond those required by {@link #setLimit(int)}.
+   *
+   * Defaults to -1 if not specified, which triggers some heuristic guessing based on other settings.
+   */
+  public TermsFacetMap setOverRequest(int numExtraBuckets) {
+    if (numExtraBuckets < -1) {
+      throw new IllegalArgumentException("Parameter 'numExtraBuckets' must be >= -1");
+    }
+    put("overrequest", numExtraBuckets);
+    return this;
+  }
+
+  /**
+   * Indicates whether this facet should use distributed facet refining.
+   *
+   * "Distributed facet refining" is a second, optional stage in the facet process that ensures that counts for the
+   * returned buckets are exact.  Enabling it is a tradeoff between precision and speed/performance.  Defaults to false
+   * if not specified.
+   * @param useRefining true if distributed facet refining should be used; false otherwise
+   */
+  public TermsFacetMap useDistributedFacetRefining(boolean useRefining) {
+    put("refine", useRefining);
+    return this;
+  }
+
+  /**
+   * Indicates how many extra buckets to request during distributed-facet-refining beyond those required by {@link #setLimit(int)}
+   *
+   * Defaults to -1 if not specified, which triggers some heuristic guessing based on other settings.
+   */
+  public TermsFacetMap setOverRefine(int numExtraBuckets) {
+    if (numExtraBuckets < -1) {
+      throw new IllegalArgumentException("Parameter 'numExtraBuckets' must be >= -1");
+    }
+    put("overrefine", numExtraBuckets);
+    return this;
+  }
+
+  /**
+   * Indicates that the facet results should not include any buckets with a count less than {@code minCount}.
+   *
+   * Defaults to 1 if not specified.
+   */
+  public TermsFacetMap setMinCount(int minCount) {
+    if (minCount < 1) {
+      throw new IllegalArgumentException("Parameter 'minCount' must be a positive integer");
+    }
+    put("mincount", minCount);
+    return this;
+  }
+
+  /**
+   * Indicates that Solr should create a bucket corresponding to documents missing the field used by this facet.
+   *
+   * Defaults to false if not specified.
+   *
+   * @param missingBucket true if the special "missing" bucket should be created; false otherwise
+   */
+  public TermsFacetMap includeMissingBucket(boolean missingBucket) {
+    put("missing", missingBucket);
+    return this;
+  }
+
+  /**
+   * Indicates that Solr should include the total number of buckets for this facet.
+   *
+   * Note that this is different than the number of buckets returned.  Defaults to false if not specified
+   *
+   * @param numBuckets true if the "numBuckets" field should be computed; false otherwise
+   */
+  public TermsFacetMap includeTotalNumBuckets(boolean numBuckets) {
+    put("numBuckets", numBuckets);
+    return this;
+  }
+
+  /**
+   * Creates a bucket representing the union of all other buckets.
+   *
+   * For multi-valued fields this is different than a bucket for the entire domain, since documents can belong to
+   * multiple buckets.  Defaults to false if not specified.
+   *
+   * @param shouldInclude true if the union bucket "allBuckets" should be computed; false otherwise
+   */
+  public TermsFacetMap includeAllBucketsUnionBucket(boolean shouldInclude) {
+    put("allBuckets", shouldInclude);
+    return this;
+  }
+
+  /**
+   * Indicates that the facet should only produce buckets for terms that start with the specified prefix.
+   */
+  public TermsFacetMap setTermPrefix(String termPrefix) {
+    if (termPrefix == null) {
+      throw new IllegalArgumentException("Parameter 'termPrefix' must be non-null");
+    }
+    put("prefix", termPrefix);
+    return this;
+  }
+
+  public enum FacetMethod {
+    DV("dv"), UIF("uif"), DVHASH("dvhash"), ENUM("enum"), STREAM("stream"), SMART("smart");
+
+    private final String value;
+    FacetMethod(String value) {
+      this.value = value;
+    }
+
+    public String toString() {
+      return value;
+    }
+  }
+
+  /**
+   * Indicate which method should be used to compute the facet.
+   *
+   * Defaults to "smart" if not specified, which has Solr guess which computation method will be most efficient.
+   */
+  public TermsFacetMap setFacetMethod(FacetMethod method) {
+    if (method == null) {
+      throw new IllegalArgumentException("Parameter 'method' must be non-null");
+    }
+    put("method", method.toString());
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test-files/solrj/techproducts.xml
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test-files/solrj/techproducts.xml b/solr/solrj/src/test-files/solrj/techproducts.xml
new file mode 100644
index 0000000..15650fa
--- /dev/null
+++ b/solr/solrj/src/test-files/solrj/techproducts.xml
@@ -0,0 +1,421 @@
+<!--
+ 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.
+-->
+<add>
+  <doc>
+    <field name="id">TWINX2048-3200PRO</field>
+    <field name="name">CORSAIR  XMS 2GB (2 x 1GB) 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) Dual Channel Kit System Memory - Retail</field>
+    <field name="manu">Corsair Microsystems Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">corsair</field>
+    <field name="cat">electronics</field>
+    <field name="cat">memory</field>
+    <field name="features">CAS latency 2,  2-3-3-6 timing, 2.75v, unbuffered, heat-spreader</field>
+    <field name="price">185.00</field>
+    <field name="popularity">5</field>
+    <field name="inStock">true</field>
+    <!-- San Francisco store -->
+    <field name="store">37.7752,-122.4232</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+    <!-- a field for testing payload tagged text via DelimitedPayloadTokenFilter -->
+    <field name="payloads">electronics|6.0 memory|3.0</field>
+  </doc>
+  <doc>
+    <field name="id">VS1GB400C3</field>
+    <field name="name">CORSAIR ValueSelect 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - Retail</field>
+    <field name="manu">Corsair Microsystems Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">corsair</field>
+    <field name="cat">electronics</field>
+    <field name="cat">memory</field>
+    <field name="price">74.99</field>
+    <field name="popularity">7</field>
+    <field name="inStock">true</field>
+    <!-- Dodge City store -->
+    <field name="store">37.7752,-100.0232</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+    <field name="payloads">electronics|4.0 memory|2.0</field>
+  </doc>
+  <doc>
+    <field name="id">VDBDB1A16</field>
+    <field name="name">A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM</field>
+    <field name="manu">A-DATA Technology Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">corsair</field>
+    <field name="cat">electronics</field>
+    <field name="cat">memory</field>
+    <field name="features">CAS latency 3,   2.7v</field>
+    <!-- note: price & popularity is missing on this one -->
+    <field name="popularity">0</field>
+    <field name="inStock">true</field>
+    <!-- Buffalo store -->
+    <field name="store">45.18414,-93.88141</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+    <field name="payloads">electronics|0.9 memory|0.1</field>
+  </doc>
+  <doc>
+    <field name="id">MA147LL/A</field>
+    <field name="name">Apple 60 GB iPod with Video Playback Black</field>
+    <field name="manu">Apple Computer Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">apple</field>
+    <field name="cat">electronics</field>
+    <field name="cat">music</field>
+    <field name="features">iTunes, Podcasts, Audiobooks</field>
+    <field name="features">Stores up to 15,000 songs, 25,000 photos, or 150 hours of video</field>
+    <field name="features">2.5-inch, 320x240 color TFT LCD display with LED backlight</field>
+    <field name="features">Up to 20 hours of battery life</field>
+    <field name="features">Plays AAC, MP3, WAV, AIFF, Audible, Apple Lossless, H.264 video</field>
+    <field name="features">Notes, Calendar, Phone book, Hold button, Date display, Photo wallet, Built-in games, JPEG photo playback, Upgradeable firmware, USB 2.0 compatibility, Playback speed control, Rechargeable capability, Battery level indication</field>
+    <field name="includes">earbud headphones, USB cable</field>
+    <field name="weight">5.5</field>
+    <field name="price">399.00</field>
+    <field name="popularity">10</field>
+    <field name="inStock">true</field>
+    <!-- Dodge City store -->
+    <field name="store">37.7752,-100.0232</field>
+    <field name="manufacturedate_dt">2005-10-12T08:00:00Z</field>
+  </doc>
+  <doc>
+    <field name="id">F8V7067-APL-KIT</field>
+    <field name="name">Belkin Mobile Power Cord for iPod w/ Dock</field>
+    <field name="manu">Belkin</field>
+    <!-- Join -->
+    <field name="manu_id_s">belkin</field>
+    <field name="cat">electronics</field>
+    <field name="cat">connector</field>
+    <field name="features">car power adapter, white</field>
+    <field name="weight">4.0</field>
+    <field name="price">19.95</field>
+    <field name="popularity">1</field>
+    <field name="inStock">false</field>
+    <!-- Buffalo store -->
+    <field name="store">45.18014,-93.87741</field>
+    <field name="manufacturedate_dt">2005-08-01T16:30:25Z</field>
+  </doc>
+  <doc>
+    <field name="id">IW-02</field>
+    <field name="name">iPod &amp; iPod Mini USB 2.0 Cable</field>
+    <field name="manu">Belkin</field>
+    <!-- Join -->
+    <field name="manu_id_s">belkin</field>
+    <field name="cat">electronics</field>
+    <field name="cat">connector</field>
+    <field name="features">car power adapter for iPod, white</field>
+    <field name="weight">2.0</field>
+    <field name="price">11.50</field>
+    <field name="popularity">1</field>
+    <field name="inStock">false</field>
+    <!-- San Francisco store -->
+    <field name="store">37.7752,-122.4232</field>
+    <field name="manufacturedate_dt">2006-02-14T23:55:59Z</field>
+  </doc>
+  <doc>
+    <field name="id">9885A004</field>
+    <field name="name">Canon PowerShot SD500</field>
+    <field name="manu">Canon Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">canon</field>
+    <field name="cat">electronics</field>
+    <field name="cat">camera</field>
+    <field name="features">3x zoop, 7.1 megapixel Digital ELPH</field>
+    <field name="features">movie clips up to 640x480 @30 fps</field>
+    <field name="features">2.0" TFT LCD, 118,000 pixels</field>
+    <field name="features">built in flash, red-eye reduction</field>
+    <field name="includes">32MB SD card, USB cable, AV cable, battery</field>
+    <field name="weight">6.4</field>
+    <field name="price">329.95</field>
+    <field name="popularity">7</field>
+    <field name="inStock">true</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+    <!-- Buffalo store -->
+    <field name="store">45.19614,-93.90341</field>
+  </doc>
+  <doc>
+    <field name="id">VA902B</field>
+    <field name="name">ViewSonic VA902B - flat panel display - TFT - 19"</field>
+    <field name="manu">ViewSonic Corp.</field>
+    <!-- Join -->
+    <field name="manu_id_s">viewsonic</field>
+    <field name="cat">electronics and stuff2</field>
+    <field name="features">19" TFT active matrix LCD, 8ms response time, 1280 x 1024 native resolution</field>
+    <field name="weight">190.4</field>
+    <field name="price">279.95</field>
+    <field name="popularity">6</field>
+    <field name="inStock">true</field>
+    <!-- Buffalo store -->
+    <field name="store">45.18814,-93.88541</field>
+  </doc>
+  <doc>
+    <field name="id">EN7800GTX/2DHTV/256M</field>
+    <field name="name">ASUS Extreme N7800GTX/2DHTV (256 MB)</field>
+    <!-- Denormalized -->
+    <field name="manu">ASUS Computer Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">asus</field>
+    <field name="cat">electronics</field>
+    <field name="cat">graphics card</field>
+    <field name="features">NVIDIA GeForce 7800 GTX GPU/VPU clocked at 486MHz</field>
+    <field name="features">256MB GDDR3 Memory clocked at 1.35GHz</field>
+    <field name="features">PCI Express x16</field>
+    <field name="features">Dual DVI connectors, HDTV out, video input</field>
+    <field name="features">OpenGL 2.0, DirectX 9.0</field>
+    <field name="weight">16.0</field>
+    <field name="price">479.95</field>
+    <field name="popularity">7</field>
+    <field name="store">40.7143,-74.006</field>
+    <field name="inStock">false</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
+  </doc>
+  <!-- yes, you can add more than one document at a time -->
+  <doc>
+    <field name="id">100-435805</field>
+    <field name="name">ATI Radeon X1900 XTX 512 MB PCIE Video Card</field>
+    <field name="manu">ATI Technologies</field>
+    <!-- Join -->
+    <field name="manu_id_s">ati</field>
+    <field name="cat">electronics</field>
+    <field name="cat">graphics card</field>
+    <field name="features">ATI RADEON X1900 GPU/VPU clocked at 650MHz</field>
+    <field name="features">512MB GDDR3 SDRAM clocked at 1.55GHz</field>
+    <field name="features">PCI Express x16</field>
+    <field name="features">dual DVI, HDTV, svideo, composite out</field>
+    <field name="features">OpenGL 2.0, DirectX 9.0</field>
+    <field name="weight">48.0</field>
+    <field name="price">649.99</field>
+    <field name="popularity">7</field>
+    <field name="inStock">false</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
+    <!-- NYC store -->
+    <field name="store">40.7143,-74.006</field>
+  </doc>
+  <doc>
+    <field name="id">0579B002</field>
+    <field name="name">Canon PIXMA MP500 All-In-One Photo Printer</field>
+    <field name="manu">Canon Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">canon</field>
+    <field name="cat">electronics</field>
+    <field name="cat">multifunction printer</field>
+    <field name="cat">printer</field>
+    <field name="cat">scanner</field>
+    <field name="cat">copier</field>
+    <field name="features">Multifunction ink-jet color photo printer</field>
+    <field name="features">Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi</field>
+    <field name="features">2.5" color LCD preview screen</field>
+    <field name="features">Duplex Copying</field>
+    <field name="features">Printing speed up to 29ppm black, 19ppm color</field>
+    <field name="features">Hi-Speed USB</field>
+    <field name="features">memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard</field>
+    <field name="weight">352.0</field>
+    <field name="price">179.99</field>
+    <field name="popularity">6</field>
+    <field name="inStock">true</field>
+    <!-- Buffalo store -->
+    <field name="store">45.19214,-93.89941</field>
+  </doc>
+  <doc>
+    <field name="id">3007WFP</field>
+    <field name="name">Dell Widescreen UltraSharp 3007WFP</field>
+    <field name="manu">Dell, Inc.</field>
+    <!-- Join -->
+    <field name="manu_id_s">dell</field>
+    <field name="cat">electronics and computer1</field>
+    <field name="features">30" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast</field>
+    <field name="includes">USB cable</field>
+    <field name="weight">401.6</field>
+    <field name="price">2199.0</field>
+    <field name="popularity">6</field>
+    <field name="inStock">true</field>
+    <!-- Buffalo store -->
+    <field name="store">43.17614,-90.57341</field>
+  </doc>
+  <doc>
+    <field name="id">adata</field>
+    <field name="compName_s">A-Data Technology</field>
+    <field name="address_s">46221 Landing Parkway Fremont, CA 94538</field>
+  </doc>
+  <doc>
+    <field name="id">apple</field>
+    <field name="compName_s">Apple</field>
+    <field name="address_s">1 Infinite Way, Cupertino CA</field>
+  </doc>
+  <doc>
+    <field name="id">asus</field>
+    <field name="compName_s">ASUS Computer</field>
+    <field name="address_s">800 Corporate Way Fremont, CA 94539</field>
+  </doc>
+  <doc>
+    <field name="id">ati</field>
+    <field name="compName_s">ATI Technologies</field>
+    <field name="address_s">33 Commerce Valley Drive East Thornhill, ON L3T 7N6 Canada</field>
+  </doc>
+  <doc>
+    <field name="id">belkin</field>
+    <field name="compName_s">Belkin</field>
+    <field name="address_s">12045 E. Waterfront Drive Playa Vista, CA 90094</field>
+  </doc>
+  <doc>
+    <field name="id">canon</field>
+    <field name="compName_s">Canon, Inc.</field>
+    <field name="address_s">One Canon Plaza Lake Success, NY 11042</field>
+  </doc>
+  <doc>
+    <field name="id">corsair</field>
+    <field name="compName_s">Corsair Microsystems</field>
+    <field name="address_s">46221 Landing Parkway Fremont, CA 94538</field>
+  </doc>
+  <doc>
+    <field name="id">dell</field>
+    <field name="compName_s">Dell, Inc.</field>
+    <field name="address_s">One Dell Way Round Rock, Texas 78682</field>
+  </doc>
+  <doc>
+    <field name="id">maxtor</field>
+    <field name="compName_s">Maxtor Corporation</field>
+    <field name="address_s">920 Disc Drive Scotts Valley, CA 95066</field>
+  </doc>
+  <doc>
+    <field name="id">samsung</field>
+    <field name="compName_s">Samsung Electronics Co. Ltd.</field>
+    <field name="address_s">105 Challenger Rd. Ridgefield Park, NJ 07660-0511</field>
+  </doc>
+  <doc>
+    <field name="id">viewsonic</field>
+    <field name="compName_s">ViewSonic Corp</field>
+    <field name="address_s">381 Brea Canyon Road Walnut, CA 91789-0708</field>
+  </doc>
+  <doc>
+    <field name="id">SP2514N</field>
+    <field name="name">Samsung SpinPoint P120 SP2514N - hard drive - 250 GB - ATA-133</field>
+    <field name="manu">Samsung Electronics Co. Ltd.</field>
+    <!-- Join -->
+    <field name="manu_id_s">samsung</field>
+    <field name="cat">electronics</field>
+    <field name="cat">hard drive</field>
+    <field name="features">7200RPM, 8MB cache, IDE Ultra ATA-133</field>
+    <field name="features">NoiseGuard, SilentSeek technology, Fluid Dynamic Bearing (FDB) motor</field>
+    <field name="price">92.0</field>
+    <field name="popularity">6</field>
+    <field name="inStock">true</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+    <!-- Near Oklahoma city -->
+    <field name="store">35.0752,-97.032</field>
+  </doc>
+  <doc>
+    <field name="id">6H500F0</field>
+    <field name="name">Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300</field>
+    <field name="manu">Maxtor Corp.</field>
+    <!-- Join -->
+    <field name="manu_id_s">maxtor</field>
+    <field name="cat">electronics</field>
+    <field name="cat">hard drive</field>
+    <field name="features">SATA 3.0Gb/s, NCQ</field>
+    <field name="features">8.5ms seek</field>
+    <field name="features">16MB cache</field>
+    <field name="price">350.0</field>
+    <field name="popularity">6</field>
+    <field name="inStock">true</field>
+    <!-- Buffalo store -->
+    <field name="store">45.17614,-93.87341</field>
+    <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+  </doc>
+  <doc>
+    <field name="id">USD</field>
+    <field name="name">One Dollar</field>
+    <field name="manu">Bank of America</field>
+    <field name="manu_id_s">boa</field>
+    <field name="cat">currency</field>
+    <field name="features">Coins and notes</field>
+    <field name="price_c">1,USD</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">EUR</field>
+    <field name="name">One Euro</field>
+    <field name="manu">European Union</field>
+    <field name="manu_id_s">eu</field>
+    <field name="cat">currency</field>
+    <field name="features">Coins and notes</field>
+    <field name="price_c">1,EUR</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">GBP</field>
+    <field name="name">One British Pound</field>
+    <field name="manu">U.K.</field>
+    <field name="manu_id_s">uk</field>
+    <field name="cat">currency</field>
+    <field name="features">Coins and notes</field>
+    <field name="price_c">1,GBP</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">NOK</field>
+    <field name="name">One Krone</field>
+    <field name="manu">Bank of Norway</field>
+    <field name="manu_id_s">nor</field>
+    <field name="cat">currency</field>
+    <field name="features">Coins and notes</field>
+    <field name="price_c">1,NOK</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">UTF8TEST</field>
+    <field name="name">Test with some UTF-8 encoded characters</field>
+    <field name="manu">Apache Software Foundation</field>
+    <field name="cat">software</field>
+    <field name="cat">search</field>
+    <field name="features">No accents here</field>
+    <field name="features">This is an e acute: &#xE9;</field>
+    <field name="features">eaiou with circumflexes: &#xEA;&#xE2;&#xEE;&#xF4;&#xFB;</field>
+    <field name="features">eaiou with umlauts: &#xEB;&#xE4;&#xEF;&#xF6;&#xFC;</field>
+    <field name="features">tag with escaped chars: &lt;nicetag/&gt;</field>
+    <field name="features">escaped ampersand: Bonnie &amp; Clyde</field>
+    <field name="features">Outside the BMP:&#x10308; codepoint=10308, a circle with an x inside. UTF8=f0908c88 UTF16=d800 df08</field>
+    <field name="price">0.0</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">GB18030TEST</field>
+    <field name="name">Test with some GB18030 encoded characters</field>
+    <field name="features">No accents here</field>
+    <field name="features">&#xD5;&#xE2;&#xCA;&#xC7;&#xD2;&#xBB;&#xB8;&#xF6;&#xB9;&#xA6;&#xC4;&#xDC;</field>
+    <field name="features">This is a feature (translated)</field>
+    <field name="features">&#xD5;&#xE2;&#xB7;&#xDD;&#xCE;&#xC4;&#xBC;&#xFE;&#xCA;&#xC7;&#xBA;&#xDC;&#xD3;&#xD0;&#xB9;&#xE2;&#xD4;&#xF3;</field>
+    <field name="features">This document is very shiny (translated)</field>
+    <field name="price">0.0</field>
+    <field name="inStock">true</field>
+  </doc>
+  <doc>
+    <field name="id">SOLR1000</field>
+    <field name="name">Solr, the Enterprise Search Server</field>
+    <field name="manu">Apache Software Foundation</field>
+    <field name="cat">software</field>
+    <field name="cat">search</field>
+    <field name="features">Advanced Full-Text Search Capabilities using Lucene</field>
+    <field name="features">Optimized for High Volume Web Traffic</field>
+    <field name="features">Standards Based Open Interfaces - XML and HTTP</field>
+    <field name="features">Comprehensive HTML Administration Interfaces</field>
+    <field name="features">Scalability - Efficient Replication to other Solr Search Servers</field>
+    <field name="features">Flexible and Adaptable with XML configuration and Schema</field>
+    <field name="features">Good unicode support: h&#xE9;llo (hello with an accent over the e)</field>
+    <field name="price">0.0</field>
+    <field name="popularity">10</field>
+    <field name="inStock">true</field>
+    <field name="incubationdate_dt">2006-01-17T00:00:00.000Z</field>
+  </doc>
+</add>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java b/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
index b941f2d..5fd3876 100644
--- a/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
@@ -27,11 +27,13 @@ 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.request.json.JsonQueryRequest;
+import org.apache.solr.client.solrj.request.json.TermsFacetMap;
 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.SolrDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.ExternalPaths;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +60,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
 
     ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
     up.setParam("collection", COLLECTION_NAME);
-    up.addFile(getFile("solrj/docs2.xml"), "application/xml"); // A subset of the 'techproducts' documents
+    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());
@@ -67,7 +69,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
   @Test
   public void testSimpleJsonQuery() throws Exception {
     SolrClient solrClient = cluster.getSolrClient();
-    final int expectedResults = 3;
+    final int expectedResults = 4;
 
     // tag::solrj-json-query-simple[]
     final JsonQueryRequest simpleQuery = new JsonQueryRequest()
@@ -116,7 +118,97 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
     // end::solrj-json-query-macro-expansion[]
 
     assertEquals(0, queryResponse.getStatus());
-    assertEquals(3, queryResponse.getResults().size());
+    assertEquals(5, queryResponse.getResults().size());
+  }
+
+  @Test
+  public void testSimpleJsonTermsFacet() throws Exception {
+    SolrClient solrClient = cluster.getSolrClient();
+
+    //tag::solrj-json-simple-terms-facet[]
+    final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(3);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("categories", categoryFacet);
+    QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
+    //end::solrj-json-simple-terms-facet[]
+
+    assertEquals(0, queryResponse.getStatus());
+    assertEquals(32, queryResponse.getResults().getNumFound());
+    assertEquals(10, queryResponse.getResults().size());
+    assertHasFacetWithBucketValues(queryResponse.getResponse(),"categories",
+        new FacetBucket("electronics",12),
+        new FacetBucket("currency", 4),
+        new FacetBucket("memory", 3));
+  }
+
+  @Test
+  public void testTermsFacet2() throws Exception {
+    SolrClient solrClient = cluster.getSolrClient();
+
+    //tag::solrj-json-terms-facet2[]
+    final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(5);
+    final JsonQueryRequest request = new JsonQueryRequest()
+        .setQuery("*:*")
+        .withFacet("categories", categoryFacet);
+    QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
+    //end::solrj-json-terms-facet2[]
+
+    assertEquals(0, queryResponse.getStatus());
+    assertEquals(32, queryResponse.getResults().getNumFound());
+    assertEquals(10, queryResponse.getResults().size());
+    assertHasFacetWithBucketValues(queryResponse.getResponse(),"categories",
+        new FacetBucket("electronics",12),
+        new FacetBucket("currency", 4),
+        new FacetBucket("memory", 3),
+        new FacetBucket("connector", 2),
+        new FacetBucket("graphics card", 2));
+  }
+
+  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 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"));
+    }
   }
 
 }


Mime
View raw message