diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index dcd102fc54d..c1eb005ce89 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -322,6 +322,11 @@ New Features * SOLR-2670: Added NIOFSDirectoryFactory (yonik) +* SOLR-2523: Added support in SolrJ to easily interact range with facets. + The range facet response can be parsed and is retrievable from the + QueryResponse class. The SolrQuery has convenient methods for using + range facets. (Martijn van Groningen) + Optimizations ---------------------- diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java index 5771028741f..fbdb75b55e2 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java @@ -17,13 +17,17 @@ package org.apache.solr.client.solrj; +import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.params.HighlightParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.StatsParams; import org.apache.solr.common.params.TermsParams; +import org.apache.solr.common.util.DateUtil; +import java.text.NumberFormat; +import java.util.Date; import java.util.regex.Pattern; @@ -227,6 +231,42 @@ public class SolrQuery extends ModifiableSolrParams return this; } + /** + * Add a numeric range facet. + * + * @param field The field + * @param start The start of range + * @param end The end of the range + * @param gap The gap between each count + * @return this + */ + public SolrQuery addNumericRangeFacet(String field, Number start, Number end, Number gap) { + add(FacetParams.FACET_RANGE, field); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_START), start.toString()); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_END), end.toString()); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_GAP), gap.toString()); + this.set(FacetParams.FACET, true); + return this; + } + + /** + * Add a numeric range facet. + * + * @param field The field + * @param start The start of range + * @param end The end of the range + * @param gap The gap between each count + * @return this + */ + public SolrQuery addDateRangeFacet(String field, Date start, Date end, String gap) { + add(FacetParams.FACET_RANGE, field); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_START), DateUtil.getThreadLocalDateFormat().format(start)); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_END), DateUtil.getThreadLocalDateFormat().format(end)); + add(String.format("f.%s.%s", field, FacetParams.FACET_RANGE_GAP), gap); + this.set(FacetParams.FACET, true); + return this; + } + /** get the facet fields * * @return string array of facet fields or null if not set/empty diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java index 78883284ba6..d38d8611a92 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java @@ -17,17 +17,12 @@ package org.apache.solr.client.solrj.response; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.solr.common.SolrDocumentList; -import org.apache.solr.common.util.NamedList; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.beans.DocumentObjectBinder; +import org.apache.solr.common.SolrDocumentList; +import org.apache.solr.common.util.NamedList; + +import java.util.*; /** * @@ -53,6 +48,7 @@ public class QueryResponse extends SolrResponseBase private List _facetFields = null; private List _limitingFacets = null; private List _facetDates = null; + private List _facetRanges = null; private NamedList> _facetPivot = null; // Highlight Info @@ -244,6 +240,36 @@ public class QueryResponse extends SolrResponseBase _facetDates.add(f); } } + + //Parse range facets + NamedList> rf = (NamedList>) info.get("facet_ranges"); + if (rf != null) { + _facetRanges = new ArrayList( rf.size() ); + for (Map.Entry> facet : rf) { + NamedList values = facet.getValue(); + Object rawGap = values.get("gap"); + + RangeFacet rangeFacet; + if (rawGap instanceof Number) { + Number gap = (Number) rawGap; + Number start = (Number) values.get("start"); + Number end = (Number) values.get("end"); + rangeFacet = new RangeFacet.Numeric(facet.getKey(), start, end, gap); + } else { + String gap = (String) rawGap; + Date start = (Date) values.get("start"); + Date end = (Date) values.get("end"); + rangeFacet = new RangeFacet.Date(facet.getKey(), start, end, gap); + } + + NamedList counts = (NamedList) values.get("counts"); + for (Map.Entry entry : counts) { + rangeFacet.addCount(entry.getKey(), entry.getValue()); + } + + _facetRanges.add(rangeFacet); + } + } //Parse pivot facets NamedList pf = (NamedList) info.get("facet_pivot"); @@ -329,6 +355,10 @@ public class QueryResponse extends SolrResponseBase return _facetDates; } + public List getFacetRanges() { + return _facetRanges; + } + public NamedList> getFacetPivot() { return _facetPivot; } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/RangeFacet.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/RangeFacet.java new file mode 100644 index 00000000000..5cd3c6566e4 --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/RangeFacet.java @@ -0,0 +1,108 @@ +package org.apache.solr.client.solrj.response; + +/* + * 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. + */ + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a range facet result + */ +public abstract class RangeFacet { + + private final String name; + private final List counts = new ArrayList(); + + private final B start; + private final B end; + private final G gap; + + protected RangeFacet(String name, B start, B end, G gap) { + this.name = name; + this.start = start; + this.end = end; + this.gap = gap; + } + + public void addCount(String value, int count) { + counts.add(new Count(value, count, this)); + } + + public String getName() { + return name; + } + + public List getCounts() { + return counts; + } + + public B getStart() { + return start; + } + + public B getEnd() { + return end; + } + + public G getGap() { + return gap; + } + + + public static class Numeric extends RangeFacet { + + public Numeric(String name, Number start, Number end, Number gap) { + super(name, start, end, gap); + } + + } + + public static class Date extends RangeFacet { + + public Date(String name, java.util.Date start, java.util.Date end, String gap) { + super(name, start, end, gap); + } + + } + + public static class Count { + + private final String value; + private final int count; + private final RangeFacet rangeFacet; + + public Count(String value, int count, RangeFacet rangeFacet) { + this.value = value; + this.count = count; + this.rangeFacet = rangeFacet; + } + + public String getValue() { + return value; + } + + public int getCount() { + return count; + } + + public RangeFacet getRangeFacet() { + return rangeFacet; + } + } + +} diff --git a/solr/solrj/src/test-files/solrj/sampleDateFacetResponse.xml b/solr/solrj/src/test-files/solrj/sampleDateFacetResponse.xml index 12e32c2f2b3..e43c40ce576 100644 --- a/solr/solrj/src/test-files/solrj/sampleDateFacetResponse.xml +++ b/solr/solrj/src/test-files/solrj/sampleDateFacetResponse.xml @@ -1,4 +1,78 @@ -00NOW/DAY-5DAYStruetrue*:*timestamptimestamp2+1DAYALLNOW/DAY+1DAY0000000+1DAY2008-03-12T00:00:00Z1600000000+1DAY2008-03-12T00:00:00Z000 - + + 0 + 0 + + NOW/DAY-5DAYS + true + true + *:* + + timestamp + timestamp2 + + +1DAY + ALL + NOW/DAY+1DAY + 0 + + + + + + + + + 0 + 0 + 0 + 0 + 0 + 0 + +1DAY + 2008-03-12T00:00:00Z + 16 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + 0 + +1DAY + 2008-03-12T00:00:00Z + 0 + 0 + 0 + + + + + + 3 + 0 + 0 + 0 + 0 + + 1.0 + 0.0 + 5.0 + + + + 4 + 7 + 0 + + +1YEAR + 2005-02-13T15:26:37Z + 2008-02-13T15:26:37Z + + + + \ No newline at end of file diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrQueryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrQueryTest.java index 4eedeb9a57c..275142b6d92 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrQueryTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrQueryTest.java @@ -21,6 +21,11 @@ import org.apache.lucene.util.LuceneTestCase; import org.apache.solr.common.params.FacetParams; import junit.framework.Assert; +import org.apache.solr.common.util.DateUtil; + +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; /** * @@ -106,6 +111,47 @@ public class SolrQueryTest extends LuceneTestCase { assertFalse("expected set value to be false", q.getFacetSort()); } + public void testFacetNumericRange() { + SolrQuery q = new SolrQuery("dog"); + q.addNumericRangeFacet("field", 1, 10, 1); + assertEquals("true", q.get(FacetParams.FACET)); + assertEquals("field", q.get(FacetParams.FACET_RANGE)); + assertEquals("1", q.get("f.field." + FacetParams.FACET_RANGE_START)); + assertEquals("10", q.get("f.field." + FacetParams.FACET_RANGE_END)); + assertEquals("1", q.get("f.field." + FacetParams.FACET_RANGE_GAP)); + + q = new SolrQuery("dog"); + q.addNumericRangeFacet("field", 1.0d, 10.0d, 1.0d); + assertEquals("true", q.get(FacetParams.FACET)); + assertEquals("field", q.get(FacetParams.FACET_RANGE)); + assertEquals("1.0", q.get("f.field." + FacetParams.FACET_RANGE_START)); + assertEquals("10.0", q.get("f.field." + FacetParams.FACET_RANGE_END)); + assertEquals("1.0", q.get("f.field." + FacetParams.FACET_RANGE_GAP)); + + q = new SolrQuery("dog"); + q.addNumericRangeFacet("field", 1.0f, 10.0f, 1.0f); + assertEquals("true", q.get(FacetParams.FACET)); + assertEquals("field", q.get(FacetParams.FACET_RANGE)); + assertEquals("1.0", q.get("f.field." + FacetParams.FACET_RANGE_START)); + assertEquals("10.0", q.get("f.field." + FacetParams.FACET_RANGE_END)); + assertEquals("1.0", q.get("f.field." + FacetParams.FACET_RANGE_GAP)); + } + + public void testFacetDateRange() { + SolrQuery q = new SolrQuery("dog"); + Calendar calendar = Calendar.getInstance(Locale.UK); + calendar.set(2010, 1, 1); + Date start = calendar.getTime(); + calendar.set(2011, 1, 1); + Date end = calendar.getTime(); + q.addDateRangeFacet("field", start, end, "+1MONTH"); + assertEquals("true", q.get(FacetParams.FACET)); + assertEquals("field", q.get(FacetParams.FACET_RANGE)); + assertEquals(DateUtil.getThreadLocalDateFormat().format(start), q.get("f.field." + FacetParams.FACET_RANGE_START)); + assertEquals(DateUtil.getThreadLocalDateFormat().format(end), q.get("f.field." + FacetParams.FACET_RANGE_END)); + assertEquals("+1MONTH", q.get("f.field." + FacetParams.FACET_RANGE_GAP)); + } + public void testSettersGetters() { SolrQuery q = new SolrQuery("foo"); assertEquals(10, q.setFacetLimit(10).getFacetLimit()); diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/QueryResponseTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/QueryResponseTest.java index 75d814161eb..0de8662d2a8 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/response/QueryResponseTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/QueryResponseTest.java @@ -17,19 +17,18 @@ package org.apache.solr.client.solrj.response; -import java.io.FileReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; - import junit.framework.Assert; - import org.apache.lucene.util.LuceneTestCase; import org.apache.solr.client.solrj.impl.XMLResponseParser; +import org.apache.solr.common.util.DateUtil; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.SolrResourceLoader; import org.junit.Test; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + /** * Simple test for Date facet support in QueryResponse * @@ -59,4 +58,59 @@ public class QueryResponseTest extends LuceneTestCase { // System.out.println("END: " + f.getEnd()); } } + + @Test + public void testRangeFacets() throws Exception { + XMLResponseParser parser = new XMLResponseParser(); + InputStream is = new SolrResourceLoader(null, null).openResource("sampleDateFacetResponse.xml"); + assertNotNull(is); + Reader in = new InputStreamReader(is, "UTF-8"); + NamedList response = parser.processResponse(in); + in.close(); + + QueryResponse qr = new QueryResponse(response, null); + Assert.assertNotNull(qr); + + int counter = 0; + RangeFacet.Numeric price = null; + RangeFacet.Date manufacturedateDt = null; + for (RangeFacet r : qr.getFacetRanges()){ + assertNotNull(r); + if ("price".equals(r.getName())) { + price = (RangeFacet.Numeric) r; + } else if ("manufacturedate_dt".equals(r.getName())) { + manufacturedateDt = (RangeFacet.Date) r; + } + + counter++; + } + assertEquals(2, counter); + assertNotNull(price); + assertNotNull(manufacturedateDt); + + assertEquals(0.0F, price.getStart()); + assertEquals(5.0F, price.getEnd()); + assertEquals(1.0F, price.getGap()); + assertEquals("0.0", price.getCounts().get(0).getValue()); + assertEquals(3, price.getCounts().get(0).getCount()); + assertEquals("1.0", price.getCounts().get(1).getValue()); + assertEquals(0, price.getCounts().get(1).getCount()); + assertEquals("2.0", price.getCounts().get(2).getValue()); + assertEquals(0, price.getCounts().get(2).getCount()); + assertEquals("3.0", price.getCounts().get(3).getValue()); + assertEquals(0, price.getCounts().get(3).getCount()); + assertEquals("4.0", price.getCounts().get(4).getValue()); + assertEquals(0, price.getCounts().get(4).getCount()); + + assertEquals(DateUtil.parseDate("2005-02-13T15:26:37Z"), manufacturedateDt.getStart()); + assertEquals(DateUtil.parseDate("2008-02-13T15:26:37Z"), manufacturedateDt.getEnd()); + assertEquals("+1YEAR", manufacturedateDt.getGap()); + assertEquals("2005-02-13T15:26:37Z", manufacturedateDt.getCounts().get(0).getValue()); + assertEquals(4, manufacturedateDt.getCounts().get(0).getCount()); + assertEquals("2006-02-13T15:26:37Z", manufacturedateDt.getCounts().get(1).getValue()); + assertEquals(7, manufacturedateDt.getCounts().get(1).getCount()); + assertEquals("2007-02-13T15:26:37Z", manufacturedateDt.getCounts().get(2).getValue()); + assertEquals(0, manufacturedateDt.getCounts().get(2).getCount()); + } + }