blob: 675a141dff84c54af4a7e5d4eb464c8d7e70fc37 [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.search.facet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.solr.common.util.SimpleOrderedMap;
public class FacetBucket {
@SuppressWarnings("rawtypes")
final FacetModule.FacetBucketMerger parent;
@SuppressWarnings({"rawtypes"})
final Comparable bucketValue;
final int bucketNumber; // this is just for internal correlation (the first bucket created is bucket 0, the next bucket 1, across all field buckets)
long count;
Map<String, FacetMerger> subs;
public FacetBucket(@SuppressWarnings("rawtypes") FacetModule.FacetBucketMerger parent
, @SuppressWarnings("rawtypes") Comparable bucketValue, FacetMerger.Context mcontext) {
this.parent = parent;
this.bucketValue = bucketValue;
this.bucketNumber = mcontext.getNewBucketNumber(); // TODO: we don't need bucket numbers for all buckets...
}
public long getCount() {
return count;
}
/** returns the existing merger for the given key, or null if none yet exists */
FacetMerger getExistingMerger(String key) {
if (subs == null) return null;
return subs.get(key);
}
private FacetMerger getMerger(String key, Object prototype) {
FacetMerger merger = null;
if (subs != null) {
merger = subs.get(key);
if (merger != null) return merger;
}
merger = parent.createFacetMerger(key, prototype);
if (merger != null) {
if (subs == null) {
subs = new HashMap<>();
}
subs.put(key, merger);
}
return merger;
}
public void mergeBucket(@SuppressWarnings("rawtypes") SimpleOrderedMap bucket, FacetMerger.Context mcontext) {
// todo: for refinements, we want to recurse, but not re-do stats for intermediate buckets
mcontext.setShardFlag(bucketNumber);
// drive merging off the received bucket?
for (int i=0; i<bucket.size(); i++) {
String key = bucket.getName(i);
Object val = bucket.getVal(i);
if ("count".equals(key)) {
count += ((Number)val).longValue();
continue;
}
if ("val".equals(key)) {
// this is taken care of at a higher level...
continue;
}
FacetMerger merger = getMerger(key, val);
if (merger != null) {
merger.merge( val , mcontext );
}
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
public SimpleOrderedMap getMergedBucket() {
SimpleOrderedMap out = new SimpleOrderedMap( (subs == null ? 0 : subs.size()) + 2 );
if (bucketValue != null) {
out.add("val", bucketValue);
}
out.add("count", count);
if (subs != null) {
for (Map.Entry<String,FacetMerger> mergerEntry : subs.entrySet()) {
FacetMerger subMerger = mergerEntry.getValue();
Object mergedResult = subMerger.getMergedResult();
if (null != mergedResult) {
out.add(mergerEntry.getKey(), mergedResult);
}
}
}
return out;
}
public Map<String, Object> getRefinement(FacetMerger.Context mcontext, Collection<String> refineTags) {
if (subs == null) {
return null;
}
Map<String,Object> refinement = null;
for (String tag : refineTags) {
FacetMerger subMerger = subs.get(tag);
if (subMerger != null) {
Map<String,Object> subRef = subMerger.getRefinement(mcontext);
if (subRef != null) {
if (refinement == null) {
refinement = new HashMap<>(refineTags.size());
}
refinement.put(tag, subRef);
}
}
}
return refinement;
}
public Map<String, Object> getRefinement2(FacetMerger.Context mcontext, Collection<String> refineTags) {
// TODO - partial results should turn off refining!!!
boolean parentMissing = mcontext.bucketWasMissing();
// TODO: this is a redundant check for many types of facets... only do on field faceting
if (!parentMissing) {
// if parent bucket wasn't missing, check if this bucket was.
// this really only needs checking on certain buckets... (like terms facet)
boolean sawThisBucket = mcontext.getShardFlag(bucketNumber);
if (!sawThisBucket) {
mcontext.setBucketWasMissing(true);
}
} else {
// if parent bucket was missing, then we should be too
assert !mcontext.getShardFlag(bucketNumber);
}
Map<String,Object> refinement = null;
if (!mcontext.bucketWasMissing()) {
// this is just a pass-through bucket... see if there is anything to do at all
if (subs == null || refineTags.isEmpty()) {
return null;
}
} else {
// for missing bucket, go over all sub-facts
refineTags = null;
refinement = new HashMap<>(4);
if (bucketValue != null) {
refinement.put("_v", bucketValue);
}
refinement.put("_m",1);
}
// TODO: listing things like sub-facets that have no field facets are redundant
// (we only need facet that have variable values)
for (Map.Entry<String,FacetMerger> sub : subs.entrySet()) {
if (refineTags != null && !refineTags.contains(sub.getKey())) {
continue;
}
Map<String,Object> subRef = sub.getValue().getRefinement(mcontext);
if (subRef != null) {
if (refinement == null) {
refinement = new HashMap<>(4);
}
refinement.put(sub.getKey(), subRef);
}
}
// reset the "bucketMissing" flag on the way back out.
mcontext.setBucketWasMissing(parentMissing);
return refinement;
}
}