/*
* Copyright (2005-2012) Schibsted ASA
* This file is part of Possom.
*
* Possom is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Possom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Possom. If not, see <http://www.gnu.org/licenses/>.
*
*/
package no.sesat.search.run;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import no.sesat.commons.ioc.BaseContext;
import no.sesat.commons.ioc.ContextWrapper;
import no.sesat.search.datamodel.DataModel;
import no.sesat.search.datamodel.generic.StringDataObject;
import no.sesat.search.query.analyser.AnalysisRule;
import no.sesat.search.query.analyser.AnalysisRuleFactory;
import no.sesat.search.mode.command.SearchCommand;
import no.sesat.search.mode.config.SearchConfiguration;
import no.sesat.search.result.ResultItem;
import no.sesat.search.result.ResultList;
import no.sesat.search.site.Site;
import no.sesat.search.site.SiteContext;
import no.sesat.search.site.SiteKeyedFactoryInstantiationException;
import no.sesat.search.view.config.SearchTab.EnrichmentHint;
import org.apache.log4j.Logger;
/**
* A RunningQuery implementing
* - Query Analysis,
* - Enrichments, and
* - RSS support.
*
* @version <tt>$Id$</tt>
*/
public class RunningQueryImpl extends AbstractRunningQuery implements RunningQuery {
// Constants -----------------------------------------------------
//FIXME: added since we had problems using the url-rewrite rules.
public static final String PARAM_LAYOUT_OLD = "output";
private static final Logger LOG = Logger.getLogger(RunningQueryImpl.class);
private static final Logger ANALYSIS_LOG = Logger.getLogger("no.sesat.search.analyzer.Analysis");
// Attributes ----------------------------------------------------
private final AnalysisRuleFactory rules;
/** */
protected final DataModel datamodel;
private final Map<String,Integer> scores = new HashMap<String,Integer>();
private final Map<String,Integer> scoresByRule = new HashMap<String,Integer>();
private final StringBuilder analysisReport;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
/**
* Create a new RunningQuery instance.
*
* @param cxt
* @param query
* @throws no.sesat.search.site.SiteKeyedFactoryInstantiationException
*/
public RunningQueryImpl(
final Context cxt,
final String query) throws SiteKeyedFactoryInstantiationException {
super(cxt, query);
this.datamodel = cxt.getDataModel();
LOG.trace("RunningQuery(cxt," + query + ')');
rules = AnalysisRuleFactory.instanceOf(ContextWrapper.wrap(
AnalysisRuleFactory.Context.class,
context,
new SiteContext(){
@Override
public Site getSite() {
return datamodel.getSite().getSite();
}
},
new BaseContext(){
public String getUniqueId(){
return datamodel.getParameters().getUniqueId();
}
}));
analysisReport = new StringBuilder(" <analyse><query>" + datamodel.getQuery().getXmlEscaped() + "</query>\n");
}
// Public --------------------------------------------------------
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
@Override
protected Collection<SearchCommand> buildCommands(){
final Collection<SearchCommand> commands = super.buildCommands();
ANALYSIS_LOG.info(analysisReport.toString() + " </analyse>");
return commands;
}
@Override
protected boolean addCommand(final SearchCommand.Context searchCmdCxt){
boolean result = false;
final SearchConfiguration conf = searchCmdCxt.getSearchConfiguration();
final EnrichmentHint eHint = context.getSearchTab().getEnrichmentByCommand(conf.getId());
if (eHint != null && !datamodel.getQuery().getQuery().isBlank()) {
// search command marked as an enrichment
if(useEnrichment(eHint, searchCmdCxt, analysisReport)){
result = true;
}
}else{
// normal search command
result = super.addCommand(searchCmdCxt);
}
return result;
}
@Override
protected boolean postProcessTask(
final Future<ResultList<ResultItem>> task,
final Map<Future<ResultList<ResultItem>>,SearchCommand> results)
throws ExecutionException, InterruptedException {
final ResultList<ResultItem> searchResult = task.get();
// Information we need about and for the enrichment
final SearchCommand command = results.get(task);
final SearchConfiguration config = command.getSearchConfiguration();
final String name = config.getId();
final EnrichmentHint eHint = context.getSearchTab().getEnrichmentByCommand(name);
final float score = scores.get(name) != null
? scores.get(name) * eHint.getWeight()
: 0;
// score
if(eHint != null && searchResult.getHitCount() > 0 && score >= eHint.getThreshold()) {
searchResult.addField(EnrichmentHint.NAME_KEY, name);
searchResult.addObjectField(EnrichmentHint.SCORE_KEY, score);
searchResult.addObjectField(EnrichmentHint.HINT_KEY, eHint);
for(Map.Entry<String,String> property : eHint.getProperties().entrySet()){
searchResult.addObjectField(property.getKey(), property.getValue());
}
}
return super.postProcessTask(task, results);
}
/** Overridden to also include enrichment searches.
*
* @return collection of SearchConfigurations applicable to this running query.
*/
@Override
protected Collection<SearchConfiguration> applicableSearchConfigurations(){
final Collection<SearchConfiguration> applicableSearchConfigurations = super.applicableSearchConfigurations();
if(!isRss() && null == datamodel.getParameters().getValue(PARAM_COMMANDS)){
for (SearchConfiguration conf : context.getSearchMode().getSearchConfigurations()) {
// check for alwaysRun or for a possible enrichment (since its scoring will be the final indicator)
boolean applicable = !conf.isAlwaysRun()
&& (null != context.getSearchTab().getEnrichmentByCommand(conf.getId())
&& !datamodel.getQuery().getQuery().isBlank());
// add search configuration if applicable
if(applicable){
applicableSearchConfigurations.add(conf);
}
}
}
return applicableSearchConfigurations;
}
// Private -------------------------------------------------------
private boolean useEnrichment(
final EnrichmentHint eHint,
final SearchCommand.Context searchCmdCxt,
final StringBuilder analysisReport){
boolean result = false;
final SearchConfiguration config = searchCmdCxt.getSearchConfiguration();
final Map<String,StringDataObject> parameters = datamodel.getParameters().getValues();
// TODO 'collapse' is not a possom standard. standardise or move out.
final boolean collapse = null == parameters.get("collapse")
|| "".equals(parameters.get("collapse").getString());
if (context.getSearchMode().isAnalysis() && collapse && eHint.getWeight() > 0){
int score = eHint.getBaseScore();
if(null != eHint.getRule()){
final AnalysisRule rule = rules.getRule(eHint.getRule());
if (null == scoresByRule.get(eHint.getRule())) {
final StringBuilder analysisRuleReport = new StringBuilder();
score += rule.evaluate(datamodel.getQuery().getQuery(),
ContextWrapper.wrap(
AnalysisRule.Context.class,
new BaseContext(){
public String getRuleName(){
return eHint.getRule();
}
public Appendable getReportBuffer(){
return analysisRuleReport;
}
},
searchCmdCxt));
scoresByRule.put(eHint.getRule(), score);
analysisReport.append(analysisRuleReport);
LOG.debug("Score for " + config.getId() + " is " + score);
} else {
score = scoresByRule.get(eHint.getRule());
}
}
scores.put(config.getId(), score);
result = score >= eHint.getThreshold();
}
return config.isAlwaysRun() || result;
}
private boolean isRss() {
final StringDataObject outputParam = datamodel.getParameters().getValue(PARAM_LAYOUT);
return null != outputParam && "rss".equals(outputParam.getString());
}
// Inner classes -------------------------------------------------
}