Programmer : CloudView Programmer : Customizing CloudView : Customizing Search Processing
 
Customizing Search Processing
 
How Are Search Queries Processed
Add Custom Query Processors or Prefix Handlers
You may need to customize the way queries are processed. This section focuses only on the processing of queries, once sent to the Search API. Customization points are possible, especially when using the Mashup API and the Mashup UI.
Note: For more information, see "Configuring Search Queries" in the Exalead CloudView Configuration Guide.
How Are Search Queries Processed
Add Custom Query Processors or Prefix Handlers
How Are Search Queries Processed
This section shows the overall query processing workflow as well as a detailed view of the query processing runner.
Figure 1. Query Processing Workflow
The following schema summarizes the workflow at the Query Processing Runner level.
Figure 2. Focus on Query Processing Runner
Add Custom Query Processors or Prefix Handlers
To customize your query, you may add either custom query processors or custom prefix handlers at specific stages of the user query processing.
Custom query processors apply to the whole AST. Custom prefix handlers apply to a subpart of the query, for example, date or text.
Where Can You Plug Them?
In the figure below, the icon indicates where you can plug custom processors:
Write Custom Query Processors
A query processor is the simplest component that you can write to customize query processing. It takes the whole QueryContext as input, and allows you to modify the context (including the AST in its current state).
See Where Can You Plug Them? to know where you can add custom query processors.
Write a Query Processor
Extend the com.exalead.search.query.processors.CustomQueryProcessor class.
Enable Your Custom Query Processor
Edit the SearchLogicList.
For example, to add a preparse processor, edit the LogicRunnerCustomization node of your SearchLogic as follows:
<s2:LogicRunnerCustomization>
<s2:globalPreParseProcessors/>
<s2:preParseProcessors>
<s2:CustomProcessor classId="com.customer.processors.MyCustomProcessor">
<s2:KeyValue xmlns="exa:exa.bee" key="AConfigKey" value="A value" />
</s2:CustomProcessor>
</s2:preParseProcessors>
<s2:preLinguisticProcessors/>
<s2:preTransformProcessors/>
<s2:preMapProcessors/>
<s2:postMapProcessors/>
<s2:globalPostParseProcessors/>
<s2:globalFinalProcessors/>
</s2:LogicRunnerCustomization>
Sample Custom Query Processor
In the sample below, a custom query processor refines the query by adding access restrictions.
This query processor looks into the context (HTML parameter) and finds the profile value given by our custom UI. Depending on that value, the query processor expands the query with a specific prefix handler (perimeter) and the corresponding restricting value.
You can plug this custom module at globalPreParse or preParse processors level (before syntactic parsing, that is to say AST creation.)
package com.exalead.customcode.search;

import com.exalead.mercury.component.CVComponentDescription;
import com.exalead.mercury.component.config.CVComponentConfig;
import com.exalead.search.query.QueryContext;
import com.exalead.search.query.QueryProcessingException;
import com.exalead.search.query.node.Node;
import com.exalead.search.query.node.NodeVisitor;
import com.exalead.search.query.node.RootNode;
import com.exalead.search.query.node.UserQueryString;
import com.exalead.search.query.processors.CustomQueryProcessor;
import com.exalead.search.query.processors.QueryProcessorContext;

@CVComponentDescription("(Sample) Profile based query rewritting")
public class ProfileBasedRestrictionQueryProcessor extends CustomQueryProcessor {
public ProfileBasedRestrictionQueryProcessor(CVComponentConfig config) throws
QueryProcessingException {
super(config);
}

@Override
public void onInit(QueryProcessorContext logic) {
System.out.println("Connection to the profile DataBase onInit");
}

@Override
public void onDeinit(boolean allInstances) {
System.out.println("Close the connection from the profile DataBase onDeinit");
}

public boolean hasRestrictedAccess(String profileId){
if (profileId.equals("admin")){
return false;
}
return true;
}

@Override
public void process(QueryContext context) throws QueryProcessingException {
String profile = context.query.parameters.getParameterValue("profile");
if(profile != null){
if ( hasRestrictedAccess(profile) ){
context.currentNode = new RootNode(context.currentNode.accept(new MyQueryRewriter()));
}
}
;
}

static class MyQueryRewriter extends NodeVisitor {

@Override
public Node visit(UserQueryString queryString) {
// add a prefix handler to restrict usage
queryString.value += " perimeter:restrictive";
return queryString;
}
}
}
Write Custom Prefix Handlers
You can write your own prefix handlers to customize query processing.
It takes the prefix handler value as input, and allows you to modify the context (including the AST in its current state).
See Where Can You Plug Them? to know where you can add custom prefix handlers.
Write a Prefix Handler
Extend the com.exalead.search.query.prefix.CustomPrefixHandler class.
Create and Package Your Custom Prefix Handler
1. Develop your prefix handler.
Recommendation: Use the Eclipse plugin. See Develop and Deploy Components Using the Eclipse Plugin.
2. Package it as a plugin to deploy in Exalead CloudView. See Packaging Custom Components as Plugins.
3. Apply prefix handlers to your Exalead CloudView configuration. See "The different types of prefix handlers" in the Exalead CloudView Configuration Guide .
Sample Custom Prefix Handler
The Exalead CloudView default behavior is to use the AND operator between words. In this sample, you use a prefix handler to change it.
You can replace the default AND operator by custom operators (XOR, OR, AND, or FUZZYAND) using a custom prefix handler.
A FUZZYAND operator uses a ratio of minimum terms to match a document:
FUZZYAND/d with d being a positive or negative number:
FUZZYAND/2 means at least two of these words.
FUZZYAND/-1 means all words but one can be missed.
FUZZYAND (without suffix). In that case, the prefix handler asks for a minimum of half words.
package com.exalead.customcode.search;

import com.exalead.config.bean.PropertyLabel;
import com.exalead.mercury.component.CVComponent;
import com.exalead.mercury.component.CVComponentDescription;
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.And;
import com.exalead.search.query.node.FuzzyAnd;
import com.exalead.search.query.node.NaryNode;
import com.exalead.search.query.node.Node;
import com.exalead.search.query.node.NodeVisitor;
import com.exalead.search.query.node.Or;
import com.exalead.search.query.node.PrefixNode;
import com.exalead.search.query.node.UserQueryChunk;
import com.exalead.search.query.node.Xor;
import com.exalead.search.query.prefix.CustomPrefixHandler;
import com.exalead.search.query.util.NodeInspector;

@PropertyLabel(value = "Change Behavior")
@CVComponentConfigClass(configClass=ChangeDefaultBehaviorPrefixHandlerConfig.class)
@CVComponentDescription("(Sample) This prefix handler allows to change the default behavior by using
another operator rather than the default one.")
public class ChangeDefaultBehaviorPrefixHandler extends CustomPrefixHandler implements CVComponent {

private String operator = null;

public ChangeDefaultBehaviorPrefixHandler(ChangeDefaultBehaviorPrefixHandlerConfig config) {
super(config);
operator = config.getOperator();
}

public void addChildren(NaryNode newNode, String[] queryParts) {

}

@Override
public Node handlePrefix(Phase phase, PrefixNode node, NodeVisitor parentVisitor,
QueryContext queryContext) throws QueryProcessingException {

if(phase.equals(Phase.PRE_LINGUISTIC)) {
int slashPos = -1;
String queryContent = NodeInspector.concatenatedContent(node).value;
String[] queryParts = queryContent.split(" ");
NaryNode newNode = null;
if (operator.contains("XOR")) {
newNode = new Xor();
} else if (operator.contains("OR")) {
newNode = new Or();
} else if (operator.contains("FUZZYAND")) {
newNode = new FuzzyAnd();
slashPos = operator.indexOf("/");
if (slashPos < 0){
((FuzzyAnd)newNode).minSuccess = (int)queryParts.length / 2;
} else if (operator.charAt(slashPos + 1)== '-'){
((FuzzyAnd)newNode).maxFailure= new Integer(operator.substring(slashPos + 2));
} else {
((FuzzyAnd)newNode).minSuccess= new Integer(operator.substring(slashPos + 1));
}
} else {
newNode = new And();
}
for (int i = 1; i < queryParts.length; i++){
newNode.addChild(new UserQueryChunk(queryParts[i]));
}
if (parentVisitor != null) {
return newNode.accept(parentVisitor);
}
return newNode;
}
if (parentVisitor !=null) {
node.setContent(node.getContent().accept(parentVisitor));
}
return node;
}
}