Created
August 27, 2012 09:20
-
-
Save dav-rob/3486870 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.elasticsearch.script.AbstractFloatSearchScript; | |
import org.elasticsearch.script.ExecutableScript; | |
import org.elasticsearch.script.NativeScriptFactory; | |
/** | |
* This class is run in isolation on the ElasticSearch Server, so can have no dependencies on other classes in CMT. | |
* | |
* Custom sorting (and, also I think, custom fields) are defined by passing a score to the Lucene sort collector. | |
* | |
* Each document stands alone and doesn't know anything about other document's scores, so a comparator based sort | |
* is not possible. The way sorting happens is that each document has its own score and then the lucene collector sorts | |
* according to these float (32 bit) scores. | |
* | |
* Although ElasticSearch has these script classes: | |
* | |
* AbstractFloatSearchScript | |
* AbstractDoubleSearchScript | |
* AbstractLongSearchScript | |
* | |
* The return value from all of these is cast to a float when the Score is passed to the Lucene sort collector. | |
* i.e. the "runAsFloat()" method is called no matter what script type is implemented. | |
* | |
* Effectively that means that we only ever have 32 bits of scores to return in order to sort the documents. | |
* | |
* In Reality it appears that we have even less than 32 bits of scores to play with: the largest range of value is | |
* | |
* 1*2^6 to 63*2^24 | |
* i.e. 2^6 to 2^32 | |
* | |
* Which is really a range of about 24 bits. | |
* | |
* @author drober20 | |
* | |
*/ | |
public class CustomColumnSorterFactory implements NativeScriptFactory{ | |
private final static Log LOG = LogFactory.getLog(CustomColumnSorterFactory.class); | |
private static HashMap<Character, Integer> charMap; | |
static { | |
HashMap<Character, Integer> charMap = new HashMap<Character, Integer>(); | |
charMap.put('0', 1); | |
charMap.put('1', 2); | |
charMap.put('2', 3); | |
charMap.put('3', 4); | |
charMap.put('4', 5); | |
charMap.put('5', 6); | |
charMap.put('6', 7); | |
charMap.put('7', 8); | |
charMap.put('8', 9); | |
charMap.put('9', 10); | |
charMap.put('A', 11); | |
charMap.put('B', 12); | |
charMap.put('C', 13); | |
charMap.put('D', 14); | |
charMap.put('E', 15); | |
charMap.put('F', 16); | |
charMap.put('G', 17); | |
charMap.put('H', 18); | |
charMap.put('I', 19); | |
charMap.put('J', 20); | |
charMap.put('K', 21); | |
charMap.put('L', 22); | |
charMap.put('M', 23); | |
charMap.put('N', 24); | |
charMap.put('O', 25); | |
charMap.put('P', 26); | |
charMap.put('Q', 27); | |
charMap.put('R', 28); | |
charMap.put('S', 29); | |
charMap.put('T', 30); | |
charMap.put('U', 31); | |
charMap.put('V', 32); | |
charMap.put('W', 33); | |
charMap.put('X', 34); | |
charMap.put('Y', 35); | |
charMap.put('Z', 36); | |
CustomColumnSorterFactory.charMap = charMap; | |
/* | |
* We have up to 2^6 -1 = 63 entries, if needed. | |
* | |
* charMap.put("1", 0); | |
charMap.put("1", 0); | |
* | |
*/ | |
} | |
@Override | |
public ExecutableScript newScript(Map<String, Object> params) { | |
LOG.info("Last string float value = " + order0_last); | |
return new ColumnSortScript(params); | |
} | |
private static String lastString = "Z";// 8 bit last string | |
//"\uffff";// 16 bit last string | |
/** | |
* This method is to allow us to sort on more then one letter in a string when we only have the | |
* option of returning a single Float (or Int or Float) value from 'sortValue' method for each | |
* document, rather than being able to compare one document with another. | |
* | |
* @param custAttrValue | |
* @return | |
*/ | |
private static Float getFirstThreeLettersFloatValue(String custAttrValue, int order) { | |
if (custAttrValue.length() == 0){ | |
throw new IllegalStateException("Null or empty strings should be initialised to 'z'"); | |
} | |
custAttrValue = custAttrValue.toUpperCase(); | |
float threeCharValue = 0; | |
int singleCharValue; | |
int sortCharLength = custAttrValue.length() < 2 ? custAttrValue.length() : 2; | |
for (int i = 0; i < sortCharLength;i++ ){ | |
Integer value = charMap.get(custAttrValue.charAt(i)); | |
if (value == null){ | |
singleCharValue = 0; | |
}else { | |
singleCharValue = value; | |
} | |
if (order == 0){ | |
if (i==0){ | |
threeCharValue += singleCharValue << 24; | |
} else if (i == 1) { | |
threeCharValue += singleCharValue << 18; | |
} | |
} else { | |
if (i==0){ | |
threeCharValue += singleCharValue << 12; | |
} else if (i == 1) { | |
threeCharValue += singleCharValue << 6; | |
} | |
} | |
} | |
return threeCharValue; | |
} | |
protected static Float order0_last = 1056964608f; // 63*2^24 | |
protected static Float order0_first = 10777216f; // much less than 1*2^24 (16777216) | |
protected static Float order1_last = 258050f; // greater than 63*2^12 | |
protected static Float order1_first = 0f; // less than 1*2^6 | |
private static Float getAsFloat(String custAttValue1, int order) { | |
try { | |
Float theFloat = Float.valueOf(custAttValue1); | |
return theFloat; | |
}catch (NumberFormatException nfe){ | |
return null; | |
} | |
} | |
public static Float getScore(List<Map> custColsMapList, String indexFieldName, boolean isASC, int order){ | |
String custAttrValue = getCustomAttrValue(custColsMapList, indexFieldName); | |
Float score = getFloatScore(custAttrValue, isASC, order); | |
String logDesc = String.format("Cust Column Value [%s], Score[%f]", custAttrValue, score); | |
LOG.info(logDesc); | |
return score; | |
} | |
public static String getCustomAttrValue(final List<Map> custColsMapList, String indexFieldName) { | |
String custAttValue = null; | |
for (Map custColsMap : custColsMapList) { | |
String ccId = (String)custColsMap.get("ccId"); | |
if (ccId.equals(indexFieldName)){ | |
//LOG.info("ccId = " + ccId ); | |
Object value = custColsMap.get("value"); | |
//LOG.info("value = " + value ); | |
if (value != null){ | |
return value.toString(); | |
} | |
} | |
} | |
return custAttValue; | |
} | |
public static Float getFloatScore(String custAttrValue, boolean isASC, int order) { | |
boolean isEmpty = custAttrValue == null || custAttrValue.trim().equals(""); | |
if (isEmpty && order == 0){ | |
return isASC ? order0_last : 0; | |
} else if (isEmpty && order == 1){ | |
return isASC ? order1_last : 0; | |
} | |
Float floatValue = null; //getAsFloat(custAttrValue, order); // we have to sacrifice true numbers to get two column sorting in the | |
// available 32 bits of column sorting space. | |
if (floatValue == null){ | |
floatValue = getFirstThreeLettersFloatValue(custAttrValue, order); | |
} | |
return floatValue; | |
} | |
private static Float getScoreForDirection(boolean isASC, Float score) { | |
if (isASC){ | |
score = 0- score; | |
} | |
return score; | |
} | |
static class ColumnSortScript extends AbstractFloatSearchScript { | |
private Map<String, Object> params; | |
public ColumnSortScript(Map<String, Object> params) { | |
this.params = params; | |
} | |
@Override | |
public float runAsFloat() { | |
// TODO Auto-generated method stub | |
return doRunAsFloat(); | |
} | |
public float doRunAsFloat() { | |
try { | |
float score = 0; | |
if (params.get("isCustomColumn") != null){ | |
score = getCustomColumnFloatValue("sortDirection", "indexFieldName", 0); | |
} else if (params.get("isNormalColumn") != null){ | |
score = getNormalColumnFloatValue("sortDirection", "indexFieldName", 0); | |
} else { | |
throw new IllegalStateException("First Sort Column must exist."); | |
} | |
//make first sort score into its' equivalent float score | |
float totalScore = score; | |
float score1 = 0; | |
if (params.get("isCustomColumn1") != null){ | |
score1 = getCustomColumnFloatValue("sortDirection1", "indexFieldName1", 1); | |
} else if (params.get("isNormalColumn1") != null){ | |
score1 = getNormalColumnFloatValue("sortDirection1", "indexFieldName1", 1); | |
} | |
totalScore += score1; | |
String logDesc = String.format("score[%f], score1[%f], totalScore[%f]", score, score1, totalScore); | |
LOG.info(logDesc); | |
return totalScore; | |
} catch (Exception e) { | |
LOG.error("Unexpected error sorting custom columns.", e); | |
return 0; | |
} | |
} | |
private float getNormalColumnFloatValue(String sortDirectionKey, String indexFieldNameKey, int order) { | |
Object sortFieldObj = params.get(indexFieldNameKey); | |
String sortField = sortFieldObj == null ? null : sortFieldObj.toString(); | |
if (sortField == null){ | |
throw new IllegalStateException("Field Name not mapped to key[" + indexFieldNameKey + "]"); | |
} | |
boolean isASC = isASC(sortDirectionKey); | |
Object fieldValueObj = source().get(sortField); | |
String fieldValue; | |
if (fieldValueObj == null){ | |
fieldValue = null; | |
} | |
else if (fieldValueObj instanceof Date){ | |
fieldValue = ((Date)fieldValueObj).getTime() + ""; | |
} else { | |
fieldValue = fieldValueObj.toString(); | |
} | |
Float score = getFloatScore(fieldValue, isASC, order); | |
score = getScoreForDirection(isASC, score); | |
String logDesc = String.format("Normal Column Value [%s], Score[%f]", fieldValue, score); | |
LOG.info(logDesc); | |
return score; | |
} | |
private Float getCustomColumnFloatValue(String sortDirectionKey, String indexFieldNameKey, int order) { | |
boolean isASC = isASC(sortDirectionKey); | |
Object customColsMapList = source().get("customColumns"); | |
//LOG.info(" Source = " + customColsMapList.getClass()); | |
if (customColsMapList == null ){ | |
float defaultReturn = defaultReturn(isASC, order); | |
//LOG.info("Default return = " + defaultReturn); | |
return defaultReturn; | |
//return getScoreForDirection(isASC, last); | |
} | |
List<Map> custColsMapList = (List<Map>) customColsMapList; | |
if (custColsMapList == null || custColsMapList.size() == 0){ | |
float defaultReturn = defaultReturn(isASC, order); | |
//LOG.info("Default return = " + defaultReturn); | |
return defaultReturn; | |
} | |
String indexFieldName = (String)params.get(indexFieldNameKey); | |
Float score = getScore(custColsMapList, indexFieldName, isASC, order); | |
score = getScoreForDirection(isASC, score); | |
return score; | |
} | |
private boolean isASC(String sortDirectionKey) { | |
String sortDirection = (String)params.get(sortDirectionKey); | |
boolean isASC = sortDirection == null || (sortDirection != null && !sortDirection.toUpperCase().trim().equals("DESC")); | |
return isASC; | |
} | |
private float defaultReturn(boolean isASC, int order) { | |
if (isASC && order == 0){ | |
return 0 - order0_last; | |
} if (isASC && order == 1){ | |
return 0 - order1_last; | |
} else if (order == 0) { | |
return order0_first; | |
} else { //if (order == 1){ | |
return order1_first; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment