/**
* 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 org.apache.lucene.search.FieldComparatorSource;
import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.PriorityQueue;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.search.MissingStringLastComparatorSource;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
public class ShardDoc extends FieldDoc {
public String shard;
public String shardAddress; // TODO
int orderInShard;
// the position of this doc within the shard... this can be used
// to short-circuit comparisons if the shard is equal, and can
// also be used to break ties within the same shard.
public Object id;
// this is currently the uniqueKeyField but
// may be replaced with internal docid in a future release.
Float score;
NamedList sortFieldValues;
// sort field values for *all* docs in a particular shard.
// this doc's values are in position orderInShard
// TODO: store the SolrDocument here?
// Store the order in the merged list for lookup when getting stored fields?
// (other components need this ordering to store data in order, like highlighting)
// but we shouldn't expose uniqueKey (have a map by it) until the stored-field
// retrieval stage.
public int positionInResponse;
// the ordinal position in the merged response arraylist
public ShardDoc(float score, Object[] fields, Object uniqueId, String shard) {
super(-1, score, fields);
this.id = uniqueId;
this.shard = shard;
}
public ShardDoc() {
super(-1, Float.NaN);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShardDoc shardDoc = (ShardDoc) o;
if (id != null ? !id.equals(shardDoc.id) : shardDoc.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString(){
return "id="+id
+" ,score="+score
+" ,shard="+shard
+" ,orderInShard="+orderInShard
+" ,positionInResponse="+positionInResponse
+" ,sortFieldValues="+sortFieldValues;
}
}
// used by distributed search to merge results.
class ShardFieldSortedHitQueue extends PriorityQueue<ShardDoc> {
/** Stores a comparator corresponding to each field being sorted by */
protected Comparator[] comparators;
/** Stores the sort criteria being used. */
protected SortField[] fields;
/** The order of these fieldNames should correspond to the order of sort field values retrieved from the shard */
protected List<String> fieldNames = new ArrayList<String>();
public ShardFieldSortedHitQueue(SortField[] fields, int size) {
final int n = fields.length;
comparators = new Comparator[n];
this.fields = new SortField[n];
for (int i = 0; i < n; ++i) {
// keep track of the named fields
int type = fields[i].getType();
if (type!=SortField.SCORE && type!=SortField.DOC) {
fieldNames.add(fields[i].getField());
}
String fieldname = fields[i].getField();
comparators[i] = getCachedComparator(fieldname, fields[i]
.getType(), fields[i].getLocale(), fields[i].getComparatorSource());
if (fields[i].getType() == SortField.STRING) {
this.fields[i] = new SortField(fieldname, fields[i].getLocale(),
fields[i].getReverse());
} else {
this.fields[i] = new SortField(fieldname, fields[i].getType(),
fields[i].getReverse());
}
//System.out.println("%%%%%%%%%%%%%%%%%% got "+fields[i].getType() +" for "+ fieldname +" fields[i].getReverse(): "+fields[i].getReverse());
}
initialize(size);
}
@Override
protected boolean lessThan(ShardDoc docA, ShardDoc docB) {
// If these docs are from the same shard, then the relative order
// is how they appeared in the response from that shard.
if (docA.shard == docB.shard) {
// if docA has a smaller position, it should be "larger" so it
// comes before docB.
// This will handle sorting by docid within the same shard
// comment this out to test comparators.
return !(docA.orderInShard < docB.orderInShard);
}
// run comparators
final int n = comparators.length;
int c = 0;
for (int i = 0; i < n && c == 0; i++) {
c = (fields[i].getReverse()) ? comparators[i].compare(docB, docA)
: comparators[i].compare(docA, docB);
}
// solve tiebreaks by comparing shards (similar to using docid)
// smaller docid's beat larger ids, so reverse the natural ordering
if (c == 0) {
c = -docA.shard.compareTo(docB.shard);
}
return c < 0;
}
Comparator getCachedComparator(String fieldname, int type, Locale locale, FieldComparatorSource factory) {
Comparator comparator = null;
switch (type) {
case SortField.SCORE:
comparator = comparatorScore(fieldname);
break;
case SortField.STRING:
if (locale != null)
comparator = comparatorStringLocale(fieldname, locale);
else
comparator = comparatorNatural(fieldname);
break;
case SortField.CUSTOM:
if (factory instanceof MissingStringLastComparatorSource){
comparator = comparatorMissingStringLast(fieldname);
} else {
// TODO: support other types such as random... is there a way to
// support generically? Perhaps just comparing Object
comparator = comparatorNatural(fieldname);
// throw new RuntimeException("Custom sort not supported factory is "+factory.getClass());
}
break;
case SortField.DOC:
// TODO: we can support this!
throw new RuntimeException("Doc sort not supported");
default:
comparator = comparatorNatural(fieldname);
break;
}
return comparator;
}
class ShardComparator implements Comparator {
String fieldName;
int fieldNum;
public ShardComparator(String fieldName) {
this.fieldName = fieldName;
this.fieldNum=0;
for (int i=0; i<fieldNames.size(); i++) {
if (fieldNames.get(i).equals(fieldName)) {
this.fieldNum = i;
break;
}
}
}
Object sortVal(ShardDoc shardDoc) {
assert(shardDoc.sortFieldValues.getName(fieldNum).equals(fieldName));
List lst = (List)shardDoc.sortFieldValues.getVal(fieldNum);
return lst.get(shardDoc.orderInShard);
}
public int compare(Object o1, Object o2) {
return 0;
}
}
static Comparator comparatorScore(final String fieldName) {
return new Comparator() {
public final int compare(final Object o1, final Object o2) {
ShardDoc e1 = (ShardDoc) o1;
ShardDoc e2 = (ShardDoc) o2;
final float f1 = e1.score;
final float f2 = e2.score;
if (f1 < f2)
return -1;
if (f1 > f2)
return 1;
return 0;
}
};
}
// The lucene natural sort ordering corresponds to numeric
// and string natural sort orderings (ascending). Since
// the PriorityQueue keeps the biggest elements by default,
// we need to reverse the natural compare ordering so that the
// smallest elements are kept instead of the largest... hence
// the negative sign on the final compareTo().
Comparator comparatorNatural(String fieldName) {
return new ShardComparator(fieldName) {
@Override
public final int compare(final Object o1, final Object o2) {
ShardDoc sd1 = (ShardDoc) o1;
ShardDoc sd2 = (ShardDoc) o2;
Comparable v1 = (Comparable)sortVal(sd1);
Comparable v2 = (Comparable)sortVal(sd2);
if (v1==v2)
return 0;
if (v1==null)
return 1;
if(v2==null)
return -1;
return -v1.compareTo(v2);
}
};
}
Comparator comparatorStringLocale(final String fieldName,
Locale locale) {
final Collator collator = Collator.getInstance(locale);
return new ShardComparator(fieldName) {
@Override
public final int compare(final Object o1, final Object o2) {
ShardDoc sd1 = (ShardDoc) o1;
ShardDoc sd2 = (ShardDoc) o2;
Comparable v1 = (Comparable)sortVal(sd1);
Comparable v2 = (Comparable)sortVal(sd2);
if (v1==v2)
return 0;
if (v1==null)
return 1;
if(v2==null)
return -1;
return -collator.compare(v1,v2);
}
};
}
Comparator comparatorMissingStringLast(final String fieldName) {
return new ShardComparator(fieldName) {
@Override
public final int compare(final Object o1, final Object o2) {
ShardDoc sd1 = (ShardDoc) o1;
ShardDoc sd2 = (ShardDoc) o2;
Comparable v1 = (Comparable)sortVal(sd1);
Comparable v2 = (Comparable)sortVal(sd2);
if (v1==v2)
return 0;
if (v1==null)
return -1;
if(v2==null)
return 1;
return -v1.compareTo(v2);
}
};
}
}