| /* |
| * 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.util.BitSet; |
| import java.util.Date; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.solr.common.params.FacetParams; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.common.util.SimpleOrderedMap; |
| import org.apache.solr.util.PivotListEntry; |
| |
| /** |
| * Models a single (value, count) pair that will exist in the collection of values for a |
| * {@link PivotFacetField} parent. This <code>PivotFacetValue</code> may itself have a |
| * nested {@link PivotFacetField} child |
| * |
| * @see PivotFacetField |
| * @see PivotFacetFieldValueCollection |
| */ |
| @SuppressWarnings("rawtypes") |
| public class PivotFacetValue { |
| |
| private final BitSet sourceShards = new BitSet(); |
| private final PivotFacetField parentPivot; |
| private final Comparable value; |
| // child can't be final, circular ref on construction |
| private PivotFacetField childPivot = null; |
| private int count; // mutable |
| private Map<String, StatsValues> statsValues = null; |
| // named list with objects because depending on how big the counts are we may get either a long or an int |
| private NamedList<Number> queryCounts; |
| private LinkedHashMap<String, RangeFacetRequest.DistribRangeFacet> rangeCounts; |
| |
| private PivotFacetValue(PivotFacetField parent, Comparable val) { |
| this.parentPivot = parent; |
| this.value = val; |
| } |
| |
| /** |
| * The value of the asssocated field modeled by this <code>PivotFacetValue</code>. |
| * May be null if this <code>PivotFacetValue</code> models the count for docs |
| * "missing" the field value. |
| * |
| * @see FacetParams#FACET_MISSING |
| */ |
| public Comparable getValue() { return value; } |
| |
| /** The count corrisponding to the value modeled by this <code>PivotFacetValue</code> */ |
| public int getCount() { return count; } |
| |
| /** |
| * The {@link PivotFacetField} corrisponding to the nested child pivot for this |
| * <code>PivotFacetValue</code>. May be null if this object is the leaf of a pivot. |
| */ |
| public PivotFacetField getChildPivot() { return childPivot; } |
| |
| |
| /** |
| * A recursive method that walks up the tree of pivot fields/values to build |
| * a list of the String representations of the values that lead down to this |
| * PivotFacetValue. |
| * |
| * @return a mutable List of the pivot value Strings leading down to and including |
| * this pivot value, will never be null but may contain nulls |
| * @see PivotFacetField#getValuePath |
| */ |
| public List<String> getValuePath() { |
| List<String> out = parentPivot.getValuePath(); |
| |
| // Note: this code doesn't play nice with custom FieldTypes -- see SOLR-6330 |
| |
| if (null == value) { |
| out.add(null); |
| } else if (value instanceof Date) { |
| out.add(((Date) value).toInstant().toString()); |
| } else { |
| out.add(value.toString()); |
| } |
| return out; |
| } |
| |
| /** |
| * A recursive method to construct a new <code>PivotFacetValue</code> object from |
| * the contents of the {@link NamedList} provided by the specified shard, relative |
| * to the specified field. |
| * |
| * If the <code>NamedList</code> contains data for a child {@link PivotFacetField} |
| * that will be recursively built as well. |
| * |
| * @see PivotFacetField#createFromListOfNamedLists |
| * @param shardNumber the id of the shard that provided this data |
| * @param rb The response builder of the current request |
| * @param parentField the parent field in the current pivot associated with this value |
| * @param pivotData the data from the specified shard for this pivot value |
| */ |
| @SuppressWarnings("unchecked") |
| public static PivotFacetValue createFromNamedList(int shardNumber, ResponseBuilder rb, PivotFacetField parentField, NamedList<Object> pivotData) { |
| |
| Comparable pivotVal = null; |
| int pivotCount = 0; |
| List<NamedList<Object>> childPivotData = null; |
| NamedList<NamedList<NamedList<?>>> statsValues = null; |
| NamedList<Number> queryCounts = null; |
| SimpleOrderedMap<SimpleOrderedMap<Object>> ranges = null; |
| |
| for (int i = 0; i < pivotData.size(); i++) { |
| String key = pivotData.getName(i); |
| Object value = pivotData.getVal(i); |
| PivotListEntry entry = PivotListEntry.get(key); |
| |
| switch (entry) { |
| |
| case VALUE: |
| pivotVal = (Comparable)value; |
| break; |
| case FIELD: |
| assert parentField.field.equals(value) |
| : "Parent Field mismatch: " + parentField.field + "!=" + value; |
| break; |
| case COUNT: |
| pivotCount = (Integer)value; |
| break; |
| case PIVOT: |
| childPivotData = (List<NamedList<Object>>)value; |
| break; |
| case STATS: |
| statsValues = (NamedList<NamedList<NamedList<?>>>) value; |
| break; |
| case QUERIES: |
| queryCounts = (NamedList<Number>) value; |
| break; |
| case RANGES: |
| ranges = (SimpleOrderedMap<SimpleOrderedMap<Object>>) value; |
| break; |
| default: |
| throw new RuntimeException("PivotListEntry contains unaccounted for item: " + entry); |
| } |
| } |
| |
| PivotFacetValue newPivotFacet = new PivotFacetValue(parentField, pivotVal); |
| newPivotFacet.count = pivotCount; |
| newPivotFacet.sourceShards.set(shardNumber); |
| if(statsValues != null) { |
| newPivotFacet.statsValues = PivotFacetHelper.mergeStats(null, statsValues, rb._statsInfo); |
| } |
| if(queryCounts != null) { |
| newPivotFacet.queryCounts = PivotFacetHelper.mergeQueryCounts(null, queryCounts); |
| } |
| if(ranges != null) { |
| newPivotFacet.rangeCounts = new LinkedHashMap<>(); |
| RangeFacetRequest.DistribRangeFacet.mergeFacetRangesFromShardResponse(newPivotFacet.rangeCounts, ranges); |
| } |
| |
| newPivotFacet.childPivot = PivotFacetField.createFromListOfNamedLists(shardNumber, rb, newPivotFacet, childPivotData); |
| |
| return newPivotFacet; |
| } |
| |
| /** |
| * A <b>NON-Recursive</b> method indicating if the specified shard has already |
| * contributed to the count for this value. |
| */ |
| public boolean shardHasContributed(int shardNum) { |
| return sourceShards.get(shardNum); |
| } |
| |
| /** |
| * A recursive method for generating a NamedList from this value suitable for |
| * including in a pivot facet response to the original distributed request. |
| * |
| * @see PivotFacetField#convertToListOfNamedLists |
| */ |
| public NamedList<Object> convertToNamedList() { |
| NamedList<Object> newList = new SimpleOrderedMap<>(); |
| newList.add(PivotListEntry.FIELD.getName(), parentPivot.field); |
| newList.add(PivotListEntry.VALUE.getName(), value); |
| newList.add(PivotListEntry.COUNT.getName(), count); |
| if(queryCounts != null) { |
| newList.add(PivotListEntry.QUERIES.getName(), queryCounts); |
| } |
| if(rangeCounts != null) { |
| SimpleOrderedMap<SimpleOrderedMap<Object>> rangeFacetOutput = new SimpleOrderedMap<>(); |
| for (Map.Entry<String, RangeFacetRequest.DistribRangeFacet> entry : rangeCounts.entrySet()) { |
| String key = entry.getKey(); |
| RangeFacetRequest.DistribRangeFacet value = entry.getValue(); |
| rangeFacetOutput.add(key, value.rangeFacet); |
| } |
| newList.add(PivotListEntry.RANGES.getName(), rangeFacetOutput); |
| } |
| if (childPivot != null && childPivot.convertToListOfNamedLists() != null) { |
| newList.add(PivotListEntry.PIVOT.getName(), childPivot.convertToListOfNamedLists()); |
| } |
| if (null != statsValues) { |
| newList.add(PivotListEntry.STATS.getName(), |
| StatsComponent.convertToResponse(statsValues)); |
| } |
| return newList; |
| } |
| |
| /** |
| * Merges in the count contributions from the specified shard for each. |
| * This method is recursive if the shard data includes sub-pivots |
| * |
| * @see PivotFacetField#contributeFromShard |
| * @see PivotFacetField#createFromListOfNamedLists |
| */ |
| public void mergeContributionFromShard(int shardNumber, ResponseBuilder rb, NamedList<Object> value) { |
| assert null != value : "can't merge in null data"; |
| |
| if (!shardHasContributed(shardNumber)) { |
| sourceShards.set(shardNumber); |
| count += PivotFacetHelper.getCount(value); |
| NamedList<NamedList<NamedList<?>>> stats = PivotFacetHelper.getStats(value); |
| if (stats != null) { |
| statsValues = PivotFacetHelper.mergeStats(statsValues, stats, rb._statsInfo); |
| } |
| NamedList<Number> shardQueryCounts = PivotFacetHelper.getQueryCounts(value); |
| if(shardQueryCounts != null) { |
| queryCounts = PivotFacetHelper.mergeQueryCounts(queryCounts, shardQueryCounts); |
| } |
| SimpleOrderedMap<SimpleOrderedMap<Object>> shardRanges = PivotFacetHelper.getRanges(value); |
| if (shardRanges != null) { |
| if (rangeCounts == null) { |
| rangeCounts = new LinkedHashMap<>(shardRanges.size() / 2); |
| } |
| RangeFacetRequest.DistribRangeFacet.mergeFacetRangesFromShardResponse(rangeCounts, shardRanges); |
| } |
| } |
| |
| List<NamedList<Object>> shardChildPivots = PivotFacetHelper.getPivots(value); |
| // sub pivot -- we may not have seen this yet depending on refinement |
| if (null == childPivot) { |
| childPivot = PivotFacetField.createFromListOfNamedLists(shardNumber, rb, this, shardChildPivots); |
| } else { |
| childPivot.contributeFromShard(shardNumber, rb, shardChildPivots); |
| } |
| } |
| |
| public String toString(){ |
| return String.format(Locale.ROOT, "F:%s V:%s Co:%d Ch?:%s", |
| parentPivot.field, value, count, (this.childPivot !=null)); |
| } |
| |
| } |