blob: 3e77f43a10460052cc4505dcb990662e8b0040e7 [file] [log] [blame]
/*
* 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 opennlp.tools.similarity.apps.solr;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import opennlp.tools.similarity.apps.HitBase;
import opennlp.tools.textsimilarity.ParseTreeChunk;
import opennlp.tools.textsimilarity.ParseTreeChunkListScorer;
import opennlp.tools.textsimilarity.SentencePairMatchResult;
import opennlp.tools.textsimilarity.chunker2matcher.ParserChunker2MatcherProcessor;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.component.SearchHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
public class SearchResultsReRankerRequestHandler extends SearchHandler {
private static final Logger LOG =
Logger.getLogger("com.become.search.requestHandlers.SearchResultsReRankerRequestHandler");
private final static int MAX_SEARCH_RESULTS = 100;
private final ParseTreeChunkListScorer parseTreeChunkListScorer = new ParseTreeChunkListScorer();
private ParserChunker2MatcherProcessor sm = null;
private static final String RESOURCE_DIR = "/home/solr/solr-4.4.0/example/src/test/resources";
//"C:/workspace/TestSolr/src/test/resources";
//"/data1/solr/example/src/test/resources";
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp){
// get query string
String requestExpression = req.getParamString();
String[] exprParts = requestExpression.split("&");
for(String part: exprParts){
if (part.startsWith("q="))
requestExpression = part;
}
String query = StringUtils.substringAfter(requestExpression, ":");
LOG.info(requestExpression);
SolrParams ps = req.getOriginalParams();
Iterator<String> iter = ps.getParameterNamesIterator();
List<String> keys = new ArrayList<>();
while(iter.hasNext()){
keys.add(iter.next());
}
List<HitBase> searchResults = new ArrayList<>();
for (int i = 0; i< MAX_SEARCH_RESULTS; i++){
String title = req.getParams().get("t"+i);
String descr = req.getParams().get("d"+i);
if(title==null || descr==null)
continue;
HitBase hit = new HitBase();
hit.setTitle(title);
hit.setAbstractText(descr);
hit.setSource(Integer.toString(i));
searchResults.add(hit);
}
/*
* http://173.255.254.250:8983/solr/collection1/reranker/?
* q=search_keywords:design+iphone+cases&fields=spend+a+day+with+a+custom+iPhone+case&fields=Add+style+to+your+every+day+fresh+design+with+a+custom+iPhone+case&fields=Add+style+to+your+every+day+with+mobile+case+for+your+family&fields=Add+style+to+your+iPhone+and+iPad&fields=Add+Apple+fashion+to+your+iPhone+and+iPad
*
*/
if (searchResults.size()<1) {
int count=0;
for(String val : exprParts){
if (val.startsWith("fields=")){
val = StringUtils.mid(val, 7, val.length());
HitBase hit = new HitBase();
hit.setTitle("");
hit.setAbstractText(val);
hit.setSource(Integer.toString(count));
searchResults.add(hit);
count++;
}
}
}
List<HitBase> reRankedResults;
query = query.replace('+', ' ');
if (tooFewKeywords(query)|| orQuery(query)){
reRankedResults = searchResults;
LOG.info("No re-ranking for "+query);
}
else
reRankedResults = calculateMatchScoreResortHits(searchResults, query);
/*
* <scores>
<score index="2">3.0005</score>
<score index="1">2.101</score>
<score index="3">2.1003333333333334</score>
<score index="4">2.00025</score>
<score index="5">1.1002</score>
</scores>
*/
StringBuilder buf = new StringBuilder();
buf.append("<scores>");
for(HitBase hit: reRankedResults){
buf.append("<score index=\"").append(hit.getSource()).append("\">").append(hit.getGenerWithQueryScore()).append("</score>");
}
buf.append("</scores>");
NamedList<Object> scoreNum = new NamedList<>();
for(HitBase hit: reRankedResults){
scoreNum.add(hit.getSource(), hit.getGenerWithQueryScore());
}
StringBuilder bufNums = new StringBuilder();
bufNums.append("order>");
for(HitBase hit: reRankedResults){
bufNums.append(hit.getSource()).append("_");
}
bufNums.append("/order>");
LOG.info("re-ranking results: "+ buf);
NamedList<Object> values = rsp.getValues();
values.remove("response");
values.add("response", scoreNum);
//values.add("new_order", bufNums.toString().trim());
rsp.setAllValues(values);
}
private boolean orQuery(String query) {
return query.indexOf('|') > -1;
}
private boolean tooFewKeywords(String query) {
String[] parts = query.split(" ");
int MAX_QUERY_LENGTH_NOT_TO_RERANK = 3;
if (parts!=null && parts.length< MAX_QUERY_LENGTH_NOT_TO_RERANK)
return true;
return false;
}
private List<HitBase> calculateMatchScoreResortHits(List<HitBase> hits,
String searchQuery) {
try {
sm = ParserChunker2MatcherProcessor.getInstance(RESOURCE_DIR);
} catch (Exception e){
LOG.severe(e.getMessage());
}
List<HitBase> newHitList = new ArrayList<>();
int count=1;
for (HitBase hit : hits) {
String snapshot = hit.getAbstractText();
snapshot += " . " + hit.getTitle();
double score = 0.0;
try {
SentencePairMatchResult matchRes = sm.assessRelevance(snapshot,
searchQuery);
List<List<ParseTreeChunk>> match = matchRes.getMatchResult(); // we need the second member
// so that when scores are the same, original order is maintained
score = parseTreeChunkListScorer.getParseTreeChunkListScore(match)+0.001/(double)count;
} catch (Exception e) {
LOG.info(e.getMessage());
e.printStackTrace();
}
hit.setGenerWithQueryScore(score);
newHitList.add(hit);
count++;
}
newHitList.sort(new HitBaseComparable());
LOG.info(newHitList.toString());
return newHitList;
}
public static class HitBaseComparable implements Comparator<HitBase> {
@Override
public int compare(HitBase o1, HitBase o2) {
return (o1.getGenerWithQueryScore() > o2.getGenerWithQueryScore() ? -1
: (o1 == o2 ? 0 : 1));
}
}
}
/*
http://dev1.exava.us:8086/solr/collection1/reranker/?q=search_keywords:I+want+style+in+my+every+day+fresh+design+iphone+cases
&t1=Personalized+iPhone+4+Cases&d1=spend+a+day+with+a+custom+iPhone+case
&t2=iPhone+Cases+to+spend+a+day&d2=Add+style+to+your+every+day+fresh+design+with+a+custom+iPhone+case
&t3=Plastic+iPhone+Cases&d3=Add+style+to+your+every+day+with+mobile+case+for+your+family
&t4=Personalized+iPhone+and+iPad+Cases&d4=Add+style+to+your+iPhone+and+iPad
&t5=iPhone+accessories+from+Apple&d5=Add+Apple+fashion+to+your+iPhone+and+iPad
http://dev1.exava.us:8086/solr/collection1/reranker/?q=search_keywords:I+want+style+in+my+every+day+fresh+design+iphone+cases&t1=Personalized+iPhone+4+Cases&d1=spend+a+day+with+a+custom+iPhone+case&t2=iPhone+Cases+to+spend+a+day&d2=Add+style+to+your+every+day+fresh+design+with+a+custom+iPhone+case&t3=Plastic+iPhone+Cases&d3=Add+style+to+your+every+day+with+mobile+case+for+your+family&t4=Personalized+iPhone+and+iPad+Cases&d4=Add+style+to+your+iPhone+and+iPad&t5=iPhone+accessories+from+Apple&d5=Add+Apple+fashion+to+your+iPhone+and+iPad
*/