blob: 096b56a13af8d877208fb2b428cf321f69dd853e [file] [log] [blame]
/*
* 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.handler.component;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.GroupParams;
import org.apache.solr.common.params.RequiredSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.schema.CurrencyFieldType;
import org.apache.solr.schema.CurrencyValue;
import org.apache.solr.schema.DatePointField;
import org.apache.solr.schema.DateRangeField;
import org.apache.solr.schema.ExchangeRateProvider;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.TrieDateField;
import org.apache.solr.schema.TrieField;
import org.apache.solr.util.DateMathParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Encapsulates a single facet.range request along with all its parameters. This class
* calculates all the ranges (gaps) required to be counted.
*/
public class RangeFacetRequest extends FacetComponent.FacetBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final SchemaField schemaField;
protected final String start;
protected final String end;
protected final String gap;
protected final boolean hardEnd;
protected final EnumSet<FacetParams.FacetRangeInclude> include;
protected final EnumSet<FacetParams.FacetRangeOther> others;
protected final FacetParams.FacetRangeMethod method;
protected final int minCount;
protected final boolean groupFacet;
protected final List<FacetRange> facetRanges;
/**
* The computed start value of this range
*/
protected final Object startObj;
/**
* The computed end value of this range taking into account facet.range.hardend
*/
protected final Object endObj;
/**
* The computed gap between each range
*/
protected final Object gapObj;
public RangeFacetRequest(ResponseBuilder rb, String f) {
super(rb, FacetParams.FACET_RANGE, f);
IndexSchema schema = rb.req.getSchema();
this.schemaField = schema.getField(facetOn);
SolrParams params = SolrParams.wrapDefaults(localParams, rb.req.getParams());
SolrParams required = new RequiredSolrParams(params);
String methodStr = params.get(FacetParams.FACET_RANGE_METHOD);
FacetParams.FacetRangeMethod method = (methodStr == null ? FacetParams.FacetRangeMethod.getDefault() : FacetParams.FacetRangeMethod.get(methodStr));
if ((schemaField.getType() instanceof DateRangeField) && method.equals(FacetParams.FacetRangeMethod.DV)) {
// the user has explicitly selected the FacetRangeMethod.DV method
log.warn("Range facet method '{}' is not supported together with field type '{}'. Will use method '{}' instead"
, FacetParams.FacetRangeMethod.DV, DateRangeField.class, FacetParams.FacetRangeMethod.FILTER);
method = FacetParams.FacetRangeMethod.FILTER;
}
if (method.equals(FacetParams.FacetRangeMethod.DV) && !schemaField.hasDocValues() && (schemaField.getType().isPointField())) {
log.warn("Range facet method '{}' is not supported on PointFields without docValues. Will use method '{}' instead"
, FacetParams.FacetRangeMethod.DV
, FacetParams.FacetRangeMethod.FILTER);
method = FacetParams.FacetRangeMethod.FILTER;
}
this.start = required.getFieldParam(facetOn, FacetParams.FACET_RANGE_START);
this.end = required.getFieldParam(facetOn, FacetParams.FACET_RANGE_END);
this.gap = required.getFieldParam(facetOn, FacetParams.FACET_RANGE_GAP);
this.minCount = params.getFieldInt(facetOn, FacetParams.FACET_MINCOUNT, 0);
this.include = FacetParams.FacetRangeInclude.parseParam
(params.getFieldParams(facetOn, FacetParams.FACET_RANGE_INCLUDE));
this.hardEnd = params.getFieldBool(facetOn, FacetParams.FACET_RANGE_HARD_END, false);
this.others = EnumSet.noneOf(FacetParams.FacetRangeOther.class);
final String[] othersP = params.getFieldParams(facetOn, FacetParams.FACET_RANGE_OTHER);
if (othersP != null && othersP.length > 0) {
for (final String o : othersP) {
others.add(FacetParams.FacetRangeOther.get(o));
}
}
this.groupFacet = params.getBool(GroupParams.GROUP_FACET, false);
if (groupFacet && method.equals(FacetParams.FacetRangeMethod.DV)) {
// the user has explicitly selected the FacetRangeMethod.DV method
log.warn("Range facet method '{}' is not supported together with '{}'. Will use method '{}' instead"
, FacetParams.FacetRangeMethod.DV, GroupParams.GROUP_FACET, FacetParams.FacetRangeMethod.FILTER);
method = FacetParams.FacetRangeMethod.FILTER;
}
this.method = method;
RangeEndpointCalculator<? extends Comparable<?>> calculator = createCalculator();
this.facetRanges = calculator.computeRanges();
this.gapObj = calculator.getGap();
this.startObj = calculator.getStart();
this.endObj = calculator.getComputedEnd();
}
/**
* Creates the right instance of {@link org.apache.solr.handler.component.RangeFacetRequest.RangeEndpointCalculator}
* depending on the field type of the schema field
*/
private RangeEndpointCalculator<? extends Comparable<?>> createCalculator() {
RangeEndpointCalculator<?> calc;
FieldType ft = schemaField.getType();
if (ft instanceof TrieField) {
switch (ft.getNumberType()) {
case FLOAT:
calc = new FloatRangeEndpointCalculator(this);
break;
case DOUBLE:
calc = new DoubleRangeEndpointCalculator(this);
break;
case INTEGER:
calc = new IntegerRangeEndpointCalculator(this);
break;
case LONG:
calc = new LongRangeEndpointCalculator(this);
break;
case DATE:
calc = new DateRangeEndpointCalculator(this, null);
break;
default:
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on Trie field of unexpected type:" + this.facetOn);
}
} else if (ft instanceof DateRangeField) {
calc = new DateRangeEndpointCalculator(this, null);
} else if (ft.isPointField()) {
switch (ft.getNumberType()) {
case FLOAT:
calc = new FloatRangeEndpointCalculator(this);
break;
case DOUBLE:
calc = new DoubleRangeEndpointCalculator(this);
break;
case INTEGER:
calc = new IntegerRangeEndpointCalculator(this);
break;
case LONG:
calc = new LongRangeEndpointCalculator(this);
break;
case DATE:
calc = new DateRangeEndpointCalculator(this, null);
break;
default:
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on Point field of unexpected type:" + this.facetOn);
}
} else if (ft instanceof CurrencyFieldType) {
calc = new CurrencyRangeEndpointCalculator(this);
} else {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"Unable to range facet on field:" + schemaField);
}
return calc;
}
/**
* @return the start of this range as specified by {@link FacetParams#FACET_RANGE_START} parameter
*/
public String getStart() {
return start;
}
/**
* The end of this facet.range as specified by {@link FacetParams#FACET_RANGE_END} parameter
* <p>
* Note that the actual computed end value can be different depending on the
* {@link FacetParams#FACET_RANGE_HARD_END} parameter. See {@link #endObj}
*/
public String getEnd() {
return end;
}
/**
* @return an {@link EnumSet} containing all the values specified via
* {@link FacetParams#FACET_RANGE_INCLUDE} parameter. Defaults to
* {@link org.apache.solr.common.params.FacetParams.FacetRangeInclude#LOWER} if no parameter
* is supplied. Includes all values from {@link org.apache.solr.common.params.FacetParams.FacetRangeInclude} enum
* if {@link FacetParams#FACET_RANGE_INCLUDE} includes
* {@link org.apache.solr.common.params.FacetParams.FacetRangeInclude#ALL}
*/
public EnumSet<FacetParams.FacetRangeInclude> getInclude() {
return include;
}
/**
* @return the gap as specified by {@link FacetParams#FACET_RANGE_GAP} parameter
*/
public String getGap() {
return gap;
}
/**
* @return the computed gap object
*/
public Object getGapObj() {
return gapObj;
}
/**
* @return the boolean value of {@link FacetParams#FACET_RANGE_HARD_END} parameter
*/
public boolean isHardEnd() {
return hardEnd;
}
/**
* @return an {@link EnumSet} of {@link org.apache.solr.common.params.FacetParams.FacetRangeOther} values
* specified by {@link FacetParams#FACET_RANGE_OTHER} parameter
*/
public EnumSet<FacetParams.FacetRangeOther> getOthers() {
return others;
}
/**
* @return the {@link org.apache.solr.common.params.FacetParams.FacetRangeMethod} to be used for computing
* ranges determined either by the value of {@link FacetParams#FACET_RANGE_METHOD} parameter
* or other internal constraints.
*/
public FacetParams.FacetRangeMethod getMethod() {
return method;
}
/**
* @return the minimum allowed count for facet ranges as specified by {@link FacetParams#FACET_MINCOUNT}
*/
public int getMinCount() {
return minCount;
}
/**
* @return the {@link SchemaField} instance representing the field on which ranges have to be calculated
*/
public SchemaField getSchemaField() {
return schemaField;
}
/**
* @return the boolean value specified by {@link GroupParams#GROUP_FACET} parameter
*/
public boolean isGroupFacet() {
return groupFacet;
}
/**
* @return a {@link List} of {@link org.apache.solr.handler.component.RangeFacetRequest.FacetRange} objects
* representing the ranges (gaps) for which range counts are to be calculated.
*/
public List<FacetRange> getFacetRanges() {
return facetRanges;
}
/**
* @return The computed start value of this range
*/
public Object getStartObj() {
return startObj;
}
/**
* The end of this facet.range as calculated using the value of facet.range.end
* parameter and facet.range.hardend. This can be different from the
* value specified in facet.range.end if facet.range.hardend=true
*/
public Object getEndObj() {
return endObj;
}
/**
* Represents a range facet response combined from all shards.
* Provides helper methods to merge facet_ranges response from a shard.
* See {@link #mergeFacetRangesFromShardResponse(LinkedHashMap, SimpleOrderedMap)}
* and {@link #mergeContributionFromShard(SimpleOrderedMap)}
*/
static class DistribRangeFacet {
public SimpleOrderedMap<Object> rangeFacet;
public DistribRangeFacet(SimpleOrderedMap<Object> rangeFacet) {
this.rangeFacet = rangeFacet;
}
/**
* Helper method to merge range facet values from a shard's response to already accumulated
* values for each range.
*
* @param rangeCounts a {@link LinkedHashMap} containing the accumulated values for each range
* keyed by the 'key' of the facet.range. Must not be null.
* @param shardRanges the facet_ranges response from a shard. Must not be null.
*/
public static void mergeFacetRangesFromShardResponse(LinkedHashMap<String, DistribRangeFacet> rangeCounts,
SimpleOrderedMap<SimpleOrderedMap<Object>> shardRanges) {
assert shardRanges != null;
assert rangeCounts != null;
for (Map.Entry<String, SimpleOrderedMap<Object>> entry : shardRanges) {
String rangeKey = entry.getKey();
RangeFacetRequest.DistribRangeFacet existing = rangeCounts.get(rangeKey);
if (existing == null) {
rangeCounts.put(rangeKey, new RangeFacetRequest.DistribRangeFacet(entry.getValue()));
} else {
existing.mergeContributionFromShard(entry.getValue());
}
}
}
/**
* Accumulates an individual facet_ranges count from a shard into global counts.
* <p>
* The implementation below uses the first encountered shard's
* facet_ranges as the basis for subsequent shards' data to be merged.
*
* @param rangeFromShard the facet_ranges response from a shard
*/
public void mergeContributionFromShard(SimpleOrderedMap<Object> rangeFromShard) {
if (rangeFacet == null) {
rangeFacet = rangeFromShard;
return;
}
@SuppressWarnings("unchecked")
NamedList<Integer> shardFieldValues
= (NamedList<Integer>) rangeFromShard.get("counts");
@SuppressWarnings("unchecked")
NamedList<Integer> existFieldValues
= (NamedList<Integer>) rangeFacet.get("counts");
for (Map.Entry<String, Integer> existPair : existFieldValues) {
final String key = existPair.getKey();
// can be null if inconsistencies in shards responses
Integer newValue = shardFieldValues.get(key);
if (null != newValue) {
Integer oldValue = existPair.getValue();
existPair.setValue(oldValue + newValue);
}
}
// merge facet.other=before/between/after/all if they exist
for (FacetParams.FacetRangeOther otherKey : FacetParams.FacetRangeOther.values()) {
if (otherKey == FacetParams.FacetRangeOther.NONE) continue;
String name = otherKey.toString();
Integer shardValue = (Integer) rangeFromShard.get(name);
if (shardValue != null && shardValue > 0) {
Integer existingValue = (Integer) rangeFacet.get(name);
// shouldn't be null
int idx = rangeFacet.indexOf(name, 0);
rangeFacet.setVal(idx, existingValue + shardValue);
}
}
}
/**
* Removes all counts under the given minCount from the accumulated facet_ranges.
* <p>
* Note: this method should only be called after all shard responses have been
* accumulated using {@link #mergeContributionFromShard(SimpleOrderedMap)}
*
* @param minCount the minimum allowed count for any range
*/
public void removeRangeFacetsUnderLimits(int minCount) {
boolean replace = false;
@SuppressWarnings("unchecked")
NamedList<Number> vals = (NamedList<Number>) rangeFacet.get("counts");
NamedList<Number> newList = new NamedList<>();
for (Map.Entry<String, Number> pair : vals) {
if (pair.getValue().longValue() >= minCount) {
newList.add(pair.getKey(), pair.getValue());
} else {
replace = true;
}
}
if (replace) {
vals.clear();
vals.addAll(newList);
}
}
}
/**
* Perhaps someday instead of having a giant "instanceof" case
* statement to pick an impl, we can add a "RangeFacetable" marker
* interface to FieldTypes and they can return instances of these
* directly from some method -- but until then, keep this locked down
* and private.
*/
private static abstract class RangeEndpointCalculator<T extends Comparable<T>> {
protected final RangeFacetRequest rfr;
protected final SchemaField field;
/**
* The end of the facet.range as determined by this calculator.
* This can be different from the facet.range.end depending on the
* facet.range.hardend parameter
*/
protected T computedEnd;
protected T start;
protected Object gap;
protected boolean computed = false;
public RangeEndpointCalculator(RangeFacetRequest rfr) {
this.rfr = rfr;
this.field = rfr.getSchemaField();
}
/** The Computed End point of all ranges, as an Object of type suitable for direct inclusion in the response data */
public Object getComputedEnd() {
assert computed;
return computedEnd;
}
/** The Start point of all ranges, as an Object of type suitable for direct inclusion in the response data */
public Object getStart() {
assert computed;
return start;
}
/**
* @return the parsed value of {@link FacetParams#FACET_RANGE_GAP} parameter. This type
* of the returned object is the boxed type of the schema field type's primitive counterpart
* except in the case of Dates in which case the returned type is just a string (because in
* case of dates the gap can either be a date or a DateMath string).
*/
public Object getGap() {
assert computed;
return gap;
}
/**
* Formats a Range endpoint for use as a range label name in the response.
* Default Impl just uses toString()
*/
public String formatValue(final T val) {
return val.toString();
}
/**
* Parses a String param into an Range endpoint value throwing
* a useful exception if not possible
*/
public final T getValue(final String rawval) {
try {
return parseVal(rawval);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't parse value " + rawval + " for field: " +
field.getName(), e);
}
}
/**
* Parses a String param into an Range endpoint.
* Can throw a low level format exception as needed.
*/
protected abstract T parseVal(final String rawval)
throws java.text.ParseException;
/**
* Parses a String param into a value that represents the gap and
* can be included in the response, throwing
* a useful exception if not possible.
* <p>
* Note: uses Object as the return type instead of T for things like
* Date where gap is just a DateMathParser string
*/
protected final Object getGap(final String gap) {
try {
return parseGap(gap);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't parse gap " + gap + " for field: " +
field.getName(), e);
}
}
/**
* Parses a String param into a value that represents the gap and
* can be included in the response.
* Can throw a low level format exception as needed.
* <p>
* Default Impl calls parseVal
*/
protected Object parseGap(final String rawval)
throws java.text.ParseException {
return parseVal(rawval);
}
/**
* Adds the String gap param to a low Range endpoint value to determine
* the corresponding high Range endpoint value, throwing
* a useful exception if not possible.
*/
public final T addGap(T value, String gap) {
try {
return parseAndAddGap(value, gap);
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can't add gap " + gap + " to value " + value +
" for field: " + field.getName(), e);
}
}
/**
* Adds the String gap param to a low Range endpoint value to determine
* the corrisponding high Range endpoint value.
* Can throw a low level format exception as needed.
*/
protected abstract T parseAndAddGap(T value, String gap)
throws java.text.ParseException;
public List<FacetRange> computeRanges() {
List<FacetRange> ranges = new ArrayList<>();
this.gap = getGap(rfr.getGap());
this.start = getValue(rfr.getStart());
// not final, hardend may change this
T end = getValue(rfr.getEnd());
if (end.compareTo(start) < 0) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"range facet 'end' comes before 'start': " + end + " < " + start);
}
final EnumSet<FacetParams.FacetRangeInclude> include = rfr.getInclude();
T low = start;
while (low.compareTo(end) < 0) {
T high = addGap(low, rfr.getGap());
if (end.compareTo(high) < 0) {
if (rfr.isHardEnd()) {
high = end;
} else {
end = high;
}
}
if (high.compareTo(low) < 0) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"range facet infinite loop (is gap negative? did the math overflow?)");
}
if (high.compareTo(low) == 0) {
throw new SolrException
(SolrException.ErrorCode.BAD_REQUEST,
"range facet infinite loop: gap is either zero, or too small relative start/end and caused underflow: " + low + " + " + rfr.getGap() + " = " + high);
}
final boolean includeLower =
(include.contains(FacetParams.FacetRangeInclude.LOWER) ||
(include.contains(FacetParams.FacetRangeInclude.EDGE) &&
0 == low.compareTo(start)));
final boolean includeUpper =
(include.contains(FacetParams.FacetRangeInclude.UPPER) ||
(include.contains(FacetParams.FacetRangeInclude.EDGE) &&
0 == high.compareTo(end)));
final String lowS = formatValue(low);
final String highS = formatValue(high);
ranges.add(new FacetRange(lowS, lowS, highS, includeLower, includeUpper));
low = high;
}
// we must update the end value in RangeFacetRequest because the end is returned
// as a separate element in the range facet response
this.computedEnd = end;
this.computed = true;
// no matter what other values are listed, we don't do
// anything if "none" is specified.
if (!rfr.getOthers().contains(FacetParams.FacetRangeOther.NONE)) {
boolean all = rfr.getOthers().contains(FacetParams.FacetRangeOther.ALL);
final String startS = formatValue(start);
final String endS = formatValue(end);
if (all || rfr.getOthers().contains(FacetParams.FacetRangeOther.BEFORE)) {
// include upper bound if "outer" or if first gap doesn't already include it
ranges.add(new FacetRange(FacetParams.FacetRangeOther.BEFORE,
null, startS, false, include.contains(FacetParams.FacetRangeInclude.OUTER) || include.contains(FacetParams.FacetRangeInclude.ALL) ||
!(include.contains(FacetParams.FacetRangeInclude.LOWER) || include.contains(FacetParams.FacetRangeInclude.EDGE))));
}
if (all || rfr.getOthers().contains(FacetParams.FacetRangeOther.AFTER)) {
// include lower bound if "outer" or if last gap doesn't already include it
ranges.add(new FacetRange(FacetParams.FacetRangeOther.AFTER,
endS, null, include.contains(FacetParams.FacetRangeInclude.OUTER) || include.contains(FacetParams.FacetRangeInclude.ALL) ||
!(include.contains(FacetParams.FacetRangeInclude.UPPER) || include.contains(FacetParams.FacetRangeInclude.EDGE)), false));
}
if (all || rfr.getOthers().contains(FacetParams.FacetRangeOther.BETWEEN)) {
ranges.add(new FacetRange(FacetParams.FacetRangeOther.BETWEEN, startS, endS,
include.contains(FacetParams.FacetRangeInclude.LOWER) || include.contains(FacetParams.FacetRangeInclude.EDGE) || include.contains(FacetParams.FacetRangeInclude.ALL),
include.contains(FacetParams.FacetRangeInclude.UPPER) || include.contains(FacetParams.FacetRangeInclude.EDGE) || include.contains(FacetParams.FacetRangeInclude.ALL)));
}
}
return ranges;
}
}
private static class FloatRangeEndpointCalculator
extends RangeEndpointCalculator<Float> {
public FloatRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest) {
super(rangeFacetRequest);
}
@Override
protected Float parseVal(String rawval) {
return Float.valueOf(rawval);
}
@Override
public Float parseAndAddGap(Float value, String gap) {
return value.floatValue() + Float.parseFloat(gap);
}
}
private static class DoubleRangeEndpointCalculator
extends RangeEndpointCalculator<Double> {
public DoubleRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest) {
super(rangeFacetRequest);
}
@Override
protected Double parseVal(String rawval) {
return Double.valueOf(rawval);
}
@Override
public Double parseAndAddGap(Double value, String gap) {
return value.doubleValue() + Double.parseDouble(gap);
}
}
private static class IntegerRangeEndpointCalculator
extends RangeEndpointCalculator<Integer> {
public IntegerRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest) {
super(rangeFacetRequest);
}
@Override
protected Integer parseVal(String rawval) {
return Integer.valueOf(rawval);
}
@Override
public Integer parseAndAddGap(Integer value, String gap) {
return value.intValue() + Integer.parseInt(gap);
}
}
private static class LongRangeEndpointCalculator
extends RangeEndpointCalculator<Long> {
public LongRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest) {
super(rangeFacetRequest);
}
@Override
protected Long parseVal(String rawval) {
return Long.valueOf(rawval);
}
@Override
public Long parseAndAddGap(Long value, String gap) {
return value.longValue() + Long.parseLong(gap);
}
}
private static class DateRangeEndpointCalculator
extends RangeEndpointCalculator<Date> {
private static final String TYPE_ERR_MSG = "SchemaField must use field type extending TrieDateField or DateRangeField";
private final Date now;
public DateRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest,
final Date now) {
super(rangeFacetRequest);
this.now = now;
if (!(field.getType() instanceof TrieDateField)
&& !(field.getType() instanceof DateRangeField)
&& !(field.getType() instanceof DatePointField)) {
throw new IllegalArgumentException(TYPE_ERR_MSG);
}
}
@Override
public String formatValue(Date val) {
return val.toInstant().toString();
}
@Override
protected Date parseVal(String rawval) {
return DateMathParser.parseMath(now, rawval);
}
@Override
protected Object parseGap(final String rawval) {
return rawval;
}
@Override
public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
final DateMathParser dmp = new DateMathParser();
dmp.setNow(value);
return dmp.parseMath(gap);
}
}
private static class CurrencyRangeEndpointCalculator
extends RangeEndpointCalculator<CurrencyValue> {
private String defaultCurrencyCode;
private ExchangeRateProvider exchangeRateProvider;
public CurrencyRangeEndpointCalculator(final RangeFacetRequest rangeFacetRequest) {
super(rangeFacetRequest);
if(!(this.field.getType() instanceof CurrencyFieldType)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Cannot perform range faceting over non CurrencyField fields");
}
defaultCurrencyCode =
((CurrencyFieldType)this.field.getType()).getDefaultCurrency();
exchangeRateProvider =
((CurrencyFieldType)this.field.getType()).getProvider();
}
@Override
protected Object parseGap(String rawval) throws java.text.ParseException {
return parseVal(rawval).strValue();
}
@Override
public String formatValue(CurrencyValue val) {
return val.strValue();
}
/** formats the value as a String since {@link CurrencyValue} is not suitable for response writers */
@Override
public Object getComputedEnd() {
assert computed;
return formatValue(computedEnd);
}
/** formats the value as a String since {@link CurrencyValue} is not suitable for response writers */
@Override
public Object getStart() {
assert computed;
return formatValue(start);
}
@Override
protected CurrencyValue parseVal(String rawval) {
return CurrencyValue.parse(rawval, defaultCurrencyCode);
}
@Override
public CurrencyValue parseAndAddGap(CurrencyValue value, String gap) {
if(value == null) {
throw new NullPointerException("Cannot perform range faceting on null CurrencyValue");
}
CurrencyValue gapCurrencyValue =
CurrencyValue.parse(gap, defaultCurrencyCode);
long gapAmount =
CurrencyValue.convertAmount(this.exchangeRateProvider,
gapCurrencyValue.getCurrencyCode(),
gapCurrencyValue.getAmount(),
value.getCurrencyCode());
return new CurrencyValue(value.getAmount() + gapAmount,
value.getCurrencyCode());
}
}
/**
* Represents a single facet range (or gap) for which the count is to be calculated
*/
public static class FacetRange {
public final FacetParams.FacetRangeOther other;
public final String name;
public final String lower;
public final String upper;
public final boolean includeLower;
public final boolean includeUpper;
private FacetRange(FacetParams.FacetRangeOther other, String name, String lower, String upper, boolean includeLower, boolean includeUpper) {
this.other = other;
this.name = name;
this.lower = lower;
this.upper = upper;
this.includeLower = includeLower;
this.includeUpper = includeUpper;
}
/**
* Construct a facet range for a {@link org.apache.solr.common.params.FacetParams.FacetRangeOther} instance
*/
public FacetRange(FacetParams.FacetRangeOther other, String lower, String upper, boolean includeLower, boolean includeUpper) {
this(other, other.toString(), lower, upper, includeLower, includeUpper);
}
/**
* Construct a facet range for the give name
*/
public FacetRange(String name, String lower, String upper, boolean includeLower, boolean includeUpper) {
this(null, name, lower, upper, includeLower, includeUpper);
}
}
}