To create your similarity query prefix handler, adapt the following code samples to your use case and package your custom prefix handler as a CVPlugin. For more information, see Packaging Custom Components as Plugins in the Exalead CloudView Programmer's Guide. Code for simple attrsimilar prefix handler
( package com.exalead.example.search;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.exalead.mercury.component.CVComponent;
import com.exalead.mercury.component.config.CVComponentConfigClass;
import com.exalead.search.query.QueryContext;
import com.exalead.search.query.QueryProcessingException;
import com.exalead.search.query.node.AttrSimilar;
import com.exalead.search.query.node.If;
import com.exalead.search.query.node.IndexOptions;
import com.exalead.search.query.node.Node;
import com.exalead.search.query.node.NodeVisitor;
import com.exalead.search.query.node.PrefixNode;
import com.exalead.search.query.node.UserQueryChunk;
import com.exalead.search.query.prefix.CustomPrefixHandler;
import com.exalead.search.query.util.LongOrDouble;
@CVComponentConfigClass(configClass=SimpleAttrSimilarPrefixHandlerConfig.class)
public class SimpleAttrSimilarPrefixHandler extends CustomPrefixHandler implements
CVComponent {
private static final Logger log = Logger.getLogger(SimpleAttrSimilarPrefixHandler.class);
private final SimpleAttrSimilarPrefixHandlerConfig config;
public SimpleAttrSimilarPrefixHandler(SimpleAttrSimilarPrefixHandlerConfig config) {
super(config);
this.config = config;
}
@Override
public Node handlePrefix(Phase phase, PrefixNode node,
NodeVisitor parentVisitor, QueryContext queryContext)
throws QueryProcessingException {
if(phase == Phase.POST_PARSE){
if (node.content instanceof UserQueryChunk) {
UserQueryChunk uqc = (UserQueryChunk) node.content;
String[] tokens = uqc.value.split(",");
IndexOptions options = new IndexOptions();
LongOrDouble filterValue = null;
String signatureField = config.getIndexField();
String signatureContext = null;
String function = config.getDistance();
//let's parse node options to override config ones
if(uqc.indexOptions != null && uqc.indexOptions.getRawOptions() != null){
for(Map.Entry<String, String> entry : uqc.indexOptions.getRawOptions().entrySet()){
if("filter_value=".equals(entry.getKey())){
filterValue = new LongOrDouble(Double.parseDouble(entry.getValue()));
} else if("index_field=".equals(entry.getKey())){
signatureField = entry.getValue();
} else if("function=".equals(entry.getKey())){
function = entry.getValue();
} else {
options.addRawOptions(entry.getKey(), entry.getValue());
}
}
}
if(filterValue == null && config.getFilterValue() != null){
filterValue = new LongOrDouble(config.getFilterValue());
}
queryContext.query.hitOrder.clone();
options.addRawOptions("function=", function);
String vfName = tokens[0];
List<LongOrDouble> signature = parseSignature(tokens[1]);
if(signatureField != null && signatureField.contains("@")){
String[] signatureFieldTokens = signatureField.split("@");
signatureField = signatureFieldTokens[1];
signatureContext = signatureFieldTokens[0];
}
return createAttrSimilarNode(vfName, options, signatureField, signatureContext,
signature, filterValue);
}
}
return node;
}
private Node createAttrSimilarNode(String vfName, IndexOptions options,
String signatureField, String signatureContext,List<LongOrDouble> signature,
LongOrDouble filterValue) throws QueryProcessingException{
Node res = null;
options.addRawOptions("name=",vfName);
if(signatureField == null){
log.error("Missing signature field config or option");
throw new QueryProcessingException("Missing signature field config or option");
}
//the #attrsimilar node
res = new AttrSimilar( signatureField, signatureContext, signature, null, null, null, options);
if(filterValue != null){
//here we create the surrounding #filter node
String filter = "@"+vfName+".value>="+filterValue.toString();
IndexOptions opts = new IndexOptions();
res = new If(res,filter, opts);
}
return res;
}
/**
*
* @param signature a space-separated list of double
* @return The List<LongOrDouble> containing the signature
*/
private List<LongOrDouble> parseSignature(String signature){
String[] tokens = signature.trim().split(" ");
List<LongOrDouble> res = new ArrayList<LongOrDouble>(tokens.length);
for(int i = 0; i<tokens.length; i++){
res.add(new LongOrDouble(Double.parseDouble(tokens[i])));
}
return res;
}
}
Code for simple attrsimilar prefix handler configuration
( package com.exalead.example.search;
import com.exalead.config.bean.IsHidden;
import com.exalead.config.bean.IsMandatory;
import com.exalead.config.bean.PropertyDescription;
import com.exalead.mercury.component.config.CVComponentConfig;
import com.exalead.search.query.util.LongOrDouble;
public class SimpleAttrSimilarPrefixHandlerConfig implements
CVComponentConfig {
public SimpleAttrSimilarPrefixHandlerConfig() {
}
private String indexField;
private String distance = "manhattan_normed";
private LongOrDouble filterValue;
@IsMandatory(false)
@PropertyDescription("The binary index field that contains the signatures."
+ "If it's a dynamic field (multi valued and storing meta names) "
+ "use the following syntax: signatureName@indexFieldName. "
+ "This value can be overridden in query option \"index_field\".")
public void setIndexField(String indexField) {
this.indexField = indexField;
}
public String getIndexField() {
return indexField;
}
@PropertyDescription("The distance function to use. "
+ "Some possible values: manhattan, manhattan_normed, euclidian, "
+ "euclidian_normed, cosine. "
+ "Normed versions of the distance must be used when the signatures "
+ "in the index have not been normed before indexing. "
+ "This value can be overridden in query option \"function\".")
public void setDistance(String distance) {
this.distance = distance;
}
public String getDistance() {
return distance;
}
@IsMandatory(false)
@PropertyDescription("The minimum similarity score that a hit must have "
+ "to match the query. "
+ "This value will generally between 0 and 1. "
+ "It can be overridden in query option \"filter_value\". "
+ "If empty, there will be no filtering based on the score.")
public void setFilterValue(Double filterValue) {
this.filterValue = new LongOrDouble(filterValue);
}
public Double getFilterValue() {
if(filterValue == null){
return null;
}
return filterValue.getDouble();
}
@IsHidden
public LongOrDouble getLongOrDoubleFilterValue() {
return filterValue;
}
}
| ||||||||||