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; } } |