Coverage Report - no.sesat.search.run.RunningQueryImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
RunningQueryImpl
0%
0/180
0%
0/128
0
RunningQueryImpl$1
0%
0/2
N/A
0
RunningQueryImpl$10
0%
0/7
N/A
0
RunningQueryImpl$11
0%
0/2
N/A
0
RunningQueryImpl$12
0%
0/2
N/A
0
RunningQueryImpl$2
0%
0/2
N/A
0
RunningQueryImpl$3
0%
0/2
N/A
0
RunningQueryImpl$4
0%
0/2
N/A
0
RunningQueryImpl$5
0%
0/2
N/A
0
RunningQueryImpl$6
0%
0/2
N/A
0
RunningQueryImpl$7
0%
0/3
N/A
0
RunningQueryImpl$8
0%
0/5
N/A
0
RunningQueryImpl$9
0%
0/3
N/A
0
 
 1  
 /*
 2  
  * Copyright (2005-2008) Schibsted Søk AS
 3  
  * This file is part of SESAT.
 4  
  *
 5  
  *   SESAT is free software: you can redistribute it and/or modify
 6  
  *   it under the terms of the GNU Affero General Public License as published by
 7  
  *   the Free Software Foundation, either version 3 of the License, or
 8  
  *   (at your option) any later version.
 9  
  *
 10  
  *   SESAT is distributed in the hope that it will be useful,
 11  
  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13  
  *   GNU Affero General Public License for more details.
 14  
  *
 15  
  *   You should have received a copy of the GNU Affero General Public License
 16  
  *   along with SESAT.  If not, see <http://www.gnu.org/licenses/>.
 17  
  *
 18  
  */
 19  
 package no.sesat.search.run;
 20  
 
 21  
 
 22  
 import no.sesat.search.query.token.DeadTokenEvaluationEngineImpl;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Arrays;
 25  
 import java.util.Collection;
 26  
 import java.util.Collections;
 27  
 import java.util.HashMap;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 import java.util.Map.Entry;
 31  
 import java.util.Properties;
 32  
 import java.util.concurrent.ExecutionException;
 33  
 import java.util.concurrent.Future;
 34  
 import java.util.concurrent.TimeoutException;
 35  
 import javax.xml.parsers.DocumentBuilder;
 36  
 import no.sesat.commons.ioc.BaseContext;
 37  
 import no.sesat.commons.ioc.ContextWrapper;
 38  
 import no.sesat.search.InfrastructureException;
 39  
 import no.sesat.search.datamodel.DataModel;
 40  
 import no.sesat.search.datamodel.DataModelFactory;
 41  
 import no.sesat.search.datamodel.access.ControlLevel;
 42  
 import no.sesat.search.datamodel.generic.DataObject;
 43  
 import no.sesat.search.datamodel.generic.MapDataObject;
 44  
 import no.sesat.search.datamodel.generic.MapDataObjectSupport;
 45  
 import no.sesat.search.datamodel.generic.StringDataObject;
 46  
 import no.sesat.search.datamodel.navigation.NavigationDataObject;
 47  
 import no.sesat.search.datamodel.query.QueryDataObject;
 48  
 import no.sesat.search.query.Clause;
 49  
 import no.sesat.search.query.analyser.AnalysisRule;
 50  
 import no.sesat.search.query.analyser.AnalysisRuleFactory;
 51  
 import no.sesat.search.query.QueryStringContext;
 52  
 import no.sesat.search.query.token.EvaluationException;
 53  
 import no.sesat.search.query.token.TokenEvaluationEngine;
 54  
 import no.sesat.search.query.token.TokenEvaluationEngineImpl;
 55  
 import no.sesat.search.mode.command.SearchCommand;
 56  
 import no.sesat.search.mode.SearchCommandFactory;
 57  
 import no.sesat.search.mode.config.BaseSearchConfiguration;
 58  
 import no.sesat.search.mode.config.SearchConfiguration;
 59  
 import no.sesat.search.mode.executor.SearchCommandExecutor;
 60  
 import no.sesat.search.mode.executor.SearchCommandExecutorFactory;
 61  
 import no.sesat.search.query.parser.AbstractQueryParserContext;
 62  
 import no.sesat.search.query.Query;
 63  
 import no.sesat.search.query.parser.QueryParser;
 64  
 import no.sesat.search.query.parser.QueryParserImpl;
 65  
 import no.sesat.search.query.token.TokenEvaluationEngineContext;
 66  
 import no.sesat.search.query.token.TokenEvaluator;
 67  
 import no.sesat.search.query.token.TokenPredicate;
 68  
 import no.sesat.search.result.NavigationItem;
 69  
 import no.sesat.search.result.ResultItem;
 70  
 import no.sesat.search.result.ResultList;
 71  
 import no.sesat.search.run.handler.NavigationRunHandlerConfig;
 72  
 import no.sesat.search.run.handler.RunHandler;
 73  
 import no.sesat.search.run.handler.RunHandlerConfig;
 74  
 import no.sesat.search.run.handler.RunHandlerFactory;
 75  
 import no.sesat.search.run.transform.RunTransformer;
 76  
 import no.sesat.search.run.transform.RunTransformerConfig;
 77  
 import no.sesat.search.run.transform.RunTransformerFactory;
 78  
 import no.sesat.search.site.Site;
 79  
 import no.sesat.search.site.SiteContext;
 80  
 import no.sesat.search.site.SiteKeyedFactoryInstantiationException;
 81  
 import no.sesat.search.site.config.BytecodeLoader;
 82  
 import no.sesat.search.site.config.DocumentLoader;
 83  
 import no.sesat.search.site.config.PropertiesLoader;
 84  
 import no.sesat.search.view.config.SearchTab.EnrichmentHint;
 85  
 import org.apache.log4j.Level;
 86  
 import org.apache.log4j.Logger;
 87  
 
 88  
 
 89  
 /**
 90  
  * Central controlling class around the individual search commands executed in any query search.
 91  
  *
 92  
  *
 93  
  *
 94  
  * @version <tt>$Id: RunningQueryImpl.java 7153 2009-01-20 13:30:03Z ssmiweve $</tt>
 95  
  */
 96  
 public class RunningQueryImpl extends AbstractRunningQuery implements RunningQuery {
 97  
 
 98  
    // Constants -----------------------------------------------------
 99  
 
 100  0
     private static final int TIMEOUT = Logger.getRootLogger().getLevel().isGreaterOrEqual(Level.INFO)
 101  
             ? 10000
 102  
             : Integer.MAX_VALUE;
 103  
 
 104  
     // TODO generic parameter key to be put into ParameterDataObject
 105  
     public static final String PARAM_LAYOUT = "layout";
 106  
     public static final String PARAM_LAYOUT_OLD = "output"; //FIXME: added since we had problems using the url-rewrite rules.
 107  
 
 108  
     // TODO generic parameter key to be put into ParameterDataObject
 109  
     private static final String PARAM_COMMANDS = "commands";
 110  
     // TODO generic parameter key to be put into ParameterDataObject
 111  
     private static final String PARAM_WAITFOR = "waitFor";
 112  
 
 113  0
     private static final Logger LOG = Logger.getLogger(RunningQueryImpl.class);
 114  0
     private static final Logger ANALYSIS_LOG = Logger.getLogger("no.sesat.search.analyzer.Analysis");
 115  0
     private static final Logger PRODUCT_LOG = Logger.getLogger("no.sesat.Product");
 116  
 
 117  
     private static final String ERR_RUN_QUERY = "Failure to run query";
 118  
     private static final String ERR_EXECUTION_ERROR = "Failure in a search command.";
 119  
     private static final String ERR_MODE_TIMEOUT = "Timeout running search commands.";
 120  
     private static final String INFO_COMMAND_COUNT = "Commands to invoke ";
 121  
 
 122  
     // Attributes ----------------------------------------------------
 123  
 
 124  
     private final AnalysisRuleFactory rules;
 125  
 
 126  
     /** have all search commands been cancelled.
 127  
      * implementation details allowing web subclasses to send correct error to client. **/
 128  0
     protected boolean allCancelled = false;
 129  
     /** */
 130  
     protected final DataModel datamodel;
 131  
     /** */
 132  
     protected final TokenEvaluationEngine engine;
 133  0
     private final Map<String,Integer> hits = new HashMap<String,Integer>();
 134  0
     private final Map<String,Integer> scores = new HashMap<String,Integer>();
 135  0
     private final Map<String,Integer> scoresByRule = new HashMap<String,Integer>();
 136  
 
 137  
     // Static --------------------------------------------------------
 138  
 
 139  
     // Constructors --------------------------------------------------
 140  
 
 141  
     /**
 142  
      * Create a new RunningQuery instance.
 143  
      *
 144  
      * @param cxt
 145  
      * @param query
 146  
      * @throws no.sesat.search.site.SiteKeyedFactoryInstantiationException
 147  
      */
 148  
     public RunningQueryImpl(
 149  
             final Context cxt,
 150  
             final String query) throws SiteKeyedFactoryInstantiationException {
 151  
 
 152  0
         super(cxt);
 153  0
         this.datamodel = cxt.getDataModel();
 154  
 
 155  0
         LOG.trace("RunningQuery(cxt," + query + ')');
 156  
 
 157  0
         final String queryStr = truncate(trimDuplicateSpaces(query));
 158  
 
 159  0
         final SiteContext siteCxt = new SiteContext(){
 160  
             public Site getSite() {
 161  0
                 return datamodel.getSite().getSite();
 162  
             }
 163  
         };
 164  
 
 165  0
         final TokenEvaluationEngine.Context tokenEvalFactoryCxt =
 166  
                 ContextWrapper.wrap(
 167  
                 TokenEvaluationEngine.Context.class,
 168  
                 context,
 169  0
                 new QueryStringContext() {
 170  
                     public String getQueryString() {
 171  0
                         return queryStr;
 172  
                     }
 173  
                 },
 174  0
                 new BaseContext() {
 175  
                     public String getUniqueId() {
 176  0
                         return datamodel.getParameters().getUniqueId();
 177  
                     }
 178  
                 },
 179  
                 siteCxt);
 180  
 
 181  0
         if(cxt.getSearchMode().isEvaluation()){
 182  0
             engine = new TokenEvaluationEngineImpl(tokenEvalFactoryCxt);
 183  
 
 184  
         }else{
 185  
             // use a dead token evaluation engine. false and stale evaluation so it is not cached.
 186  0
             engine = new DeadTokenEvaluationEngineImpl(tokenEvalFactoryCxt);
 187  
         }
 188  
 
 189  
         // queryStr parser
 190  0
         final QueryParser parser = new QueryParserImpl(new AbstractQueryParserContext() {
 191  
             public TokenEvaluationEngine getTokenEvaluationEngine() {
 192  0
                 return engine;
 193  
             }
 194  
         });
 195  
 
 196  0
         final DataModelFactory factory
 197  
                 = DataModelFactory.instanceOf(ContextWrapper.wrap(DataModelFactory.Context.class, cxt, siteCxt));
 198  
 
 199  0
         final QueryDataObject queryDO = factory.instantiate(
 200  
                 QueryDataObject.class,
 201  
                 datamodel,
 202  
                 new DataObject.Property("string", queryStr),
 203  
                 new DataObject.Property("query", parser.getQuery()));
 204  
 
 205  0
         final MapDataObject<NavigationItem> navigations
 206  
                 = new MapDataObjectSupport<NavigationItem>(Collections.<String, NavigationItem>emptyMap());
 207  0
         final NavigationDataObject navDO = factory.instantiate(
 208  
                 NavigationDataObject.class,
 209  
                 datamodel,
 210  
                 new DataObject.Property("configuration", context.getSearchTab().getNavigationConfiguration()),
 211  
                 new DataObject.Property("navigation",navigations),
 212  
                 new DataObject.Property("navigations", navigations)); // TODO bug that both single and mapped needed
 213  
 
 214  0
         datamodel.setQuery(queryDO);
 215  0
         datamodel.setNavigation(navDO);
 216  
 
 217  0
         rules = AnalysisRuleFactory.instanceOf(ContextWrapper.wrap(
 218  
                 AnalysisRuleFactory.Context.class,
 219  
                 context,
 220  
                 siteCxt,
 221  0
                 new BaseContext(){
 222  
                     public String getUniqueId(){
 223  0
                         return datamodel.getParameters().getUniqueId();
 224  
                     }
 225  
             }));
 226  
 
 227  0
     }
 228  
 
 229  
     // Public --------------------------------------------------------
 230  
 
 231  
     /**
 232  
      * Thread run. Guts of the logic behind this class.
 233  
      * XXX long method. Divide & Conquer.
 234  
      *
 235  
      * @throws InterruptedException
 236  
      */
 237  
     public void run() throws InterruptedException {
 238  
 
 239  0
         LOG.debug("run()");
 240  0
         final StringBuilder analysisReport
 241  
                 = new StringBuilder(" <analyse><query>" + datamodel.getQuery().getXmlEscaped() + "</query>\n");
 242  
 
 243  0
         final Map<String,StringDataObject> parameters = datamodel.getParameters().getValues();
 244  
 
 245  
         try {
 246  
 
 247  0
             final DataModelFactory dataModelFactory =  DataModelFactory
 248  0
                     .instanceOf(ContextWrapper.wrap(DataModelFactory.Context.class, context, new SiteContext(){
 249  
                         public Site getSite(){
 250  0
                             return datamodel.getSite().getSite();
 251  
                         }
 252  
                     }));
 253  
 
 254  
             // DataModel's ControlLevel will be RUNNING_QUERY_CONSTRUCTION
 255  
             //  Increment it onwards to SEARCH_COMMAND_CONSTRUCTION.
 256  0
             dataModelFactory.assignControlLevel(datamodel, ControlLevel.SEARCH_COMMAND_CONSTRUCTION);
 257  
 
 258  0
             final Collection<SearchCommand> commands = new ArrayList<SearchCommand>();
 259  
 
 260  0
             final SearchCommandFactory.Context scfContext = new SearchCommandFactory.Context() {
 261  
                 public Site getSite() {
 262  0
                     return context.getDataModel().getSite().getSite();
 263  
                 }
 264  
                 public BytecodeLoader newBytecodeLoader(final SiteContext site, final String name, final String jar) {
 265  0
                     return context.newBytecodeLoader(site, name, jar);
 266  
                 }
 267  
             };
 268  
 
 269  0
             final SearchCommandFactory searchCommandFactory = new SearchCommandFactory(scfContext);
 270  
 
 271  0
             for (SearchConfiguration searchConfiguration : applicableSearchConfigurations()) {
 272  
 
 273  0
                 final SearchConfiguration config = searchConfiguration;
 274  0
                 final String confName = config.getId();
 275  
 
 276  
                 try{
 277  
 
 278  0
                     hits.put(confName, Integer.valueOf(0));
 279  
 
 280  0
                     final SearchCommand.Context searchCmdCxt = ContextWrapper.wrap(
 281  
                             SearchCommand.Context.class,
 282  
                             context,
 283  0
                             new BaseContext() {
 284  
                                 public SearchConfiguration getSearchConfiguration() {
 285  0
                                     return config;
 286  
                                 }
 287  
                                 public RunningQuery getRunningQuery() {
 288  0
                                     return RunningQueryImpl.this;
 289  
                                 }
 290  
                                 public Query getQuery() {
 291  0
                                     return datamodel.getQuery().getQuery();
 292  
                                 }
 293  
                                 public TokenEvaluationEngine getTokenEvaluationEngine() {
 294  0
                                     return engine;
 295  
                                 }
 296  
                             }
 297  
                     );
 298  
 
 299  0
                     final EnrichmentHint eHint = context.getSearchTab().getEnrichmentByCommand(confName);
 300  0
                     if (eHint != null && !datamodel.getQuery().getQuery().isBlank()) {
 301  
 
 302  
                         // search command marked as an enrichment
 303  0
                         if(useEnrichment(eHint, config, searchCmdCxt, analysisReport)){
 304  0
                             commands.add(searchCommandFactory.getController(searchCmdCxt));
 305  
                         }
 306  
 
 307  
                     }else{
 308  
 
 309  
                         // normal search command
 310  0
                         commands.add(searchCommandFactory.getController(searchCmdCxt));
 311  
                     }
 312  0
                 }catch(RuntimeException re){
 313  0
                     LOG.error("Failed to add command " + confName, re);
 314  0
                 }
 315  0
             }
 316  0
             ANALYSIS_LOG.info(analysisReport.toString() + " </analyse>");
 317  
 
 318  0
             LOG.info(INFO_COMMAND_COUNT + commands.size());
 319  
 
 320  
             // mark state that we're about to execute the sub threads
 321  0
             allCancelled = commands.size() > 0;
 322  0
             boolean hitsToShow = false;
 323  
 
 324  
 
 325  
             // DataModel's ControlLevel will be SEARCH_COMMAND_CONSTRUCTION
 326  
             //  Increment it onwards to SEARCH_COMMAND_CONSTRUCTION.
 327  0
             dataModelFactory.assignControlLevel(datamodel, ControlLevel.SEARCH_COMMAND_EXECUTION);
 328  
 
 329  0
             final Map<Future<ResultList<ResultItem>>,SearchCommand> results
 330  
                     = executeSearchCommands(commands);
 331  
 
 332  
             // DataModel's ControlLevel will be SEARCH_COMMAND_CONSTRUCTION
 333  
             //  Increment it onwards to RUNNING_QUERY_RESULT_HANDLING.
 334  0
             dataModelFactory.assignControlLevel(datamodel, ControlLevel.RUNNING_QUERY_HANDLING);
 335  
 
 336  0
             if( !allCancelled ){
 337  
 
 338  0
                 final StringBuilder noHitsOutput = new StringBuilder();
 339  
 
 340  0
                 for (Future<ResultList<ResultItem>> task : results.keySet()) {
 341  
 
 342  0
                     if (task.isDone() && !task.isCancelled()) {
 343  
 
 344  
                         try{
 345  0
                             final ResultList<ResultItem> searchResult = task.get();
 346  0
                             if (searchResult != null) {
 347  
 
 348  
                                 // Information we need about and for the enrichment
 349  0
                                 final SearchCommand command = results.get(task);
 350  0
                                 final SearchConfiguration config = command.getSearchConfiguration();
 351  
 
 352  0
                                 final String name = config.getId();
 353  0
                                 final EnrichmentHint eHint = context.getSearchTab().getEnrichmentByCommand(name);
 354  
 
 355  0
                                 final float score = scores.get(name) != null
 356  
                                         ? scores.get(name) * eHint.getWeight()
 357  
                                         : 0;
 358  
 
 359  
                                 // update hit status
 360  0
                                 hitsToShow |= searchResult.getHitCount() > 0;
 361  0
                                 hits.put(name, searchResult.getHitCount());
 362  
 
 363  0
                                 if( searchResult.getHitCount() <= 0 && command.isPaginated() ){
 364  0
                                     noHitsOutput.append("<command id=\"" + config.getId()
 365  
                                             + "\" name=\""  + config.getStatisticalName()
 366  
                                             + "\" type=\"" + config.getClass().getSimpleName()
 367  
                                             + "\"/>");
 368  
                                 }
 369  
 
 370  
                                 // score
 371  0
                                 if(eHint != null && searchResult.getHitCount() > 0 && score >= eHint.getThreshold()) {
 372  
 
 373  0
                                     searchResult.addField(EnrichmentHint.NAME_KEY, name);
 374  0
                                     searchResult.addObjectField(EnrichmentHint.SCORE_KEY, score);
 375  0
                                     searchResult.addObjectField(EnrichmentHint.HINT_KEY, eHint);
 376  0
                                     for(Map.Entry<String,String> property : eHint.getProperties().entrySet()){
 377  0
                                         searchResult.addObjectField(property.getKey(), property.getValue());
 378  
                                     }
 379  
                                 }
 380  
                             }
 381  0
                         }catch(ExecutionException ee){
 382  0
                             LOG.error(ERR_EXECUTION_ERROR, ee);
 383  0
                         }
 384  
                     }
 385  
                 }
 386  
 
 387  0
                 performHandlers();
 388  
 
 389  0
                 if (!hitsToShow) {
 390  0
                     handleNoHits(noHitsOutput, parameters);
 391  
                 }
 392  
             }
 393  
 
 394  0
         } catch (Exception e) {
 395  0
             LOG.error(ERR_RUN_QUERY, e);
 396  0
             throw new InfrastructureException(e);
 397  
 
 398  0
         }
 399  0
     }
 400  
 
 401  
     // Package protected ---------------------------------------------
 402  
 
 403  
     // Protected -----------------------------------------------------
 404  
 
 405  
     /**
 406  
      *
 407  
      * @return
 408  
      */
 409  
     protected Map<String,Integer> getHits(){
 410  0
         return Collections.unmodifiableMap(hits);
 411  
     }
 412  
 
 413  
     /** Intentionally overridable. Would be nice if run-transform-spi could have influence on the result here.
 414  
      *
 415  
      * @return collection of SearchConfigurations applicable to this running query.
 416  
      */
 417  
     protected Collection<SearchConfiguration> applicableSearchConfigurations(){
 418  
 
 419  0
         final Collection<SearchConfiguration> applicableSearchConfigurations = new ArrayList<SearchConfiguration>();
 420  
 
 421  0
         final String[] explicitCommands = null != datamodel.getParameters().getValue(PARAM_COMMANDS)
 422  
                 ? datamodel.getParameters().getValue(PARAM_COMMANDS).getString().split(",")
 423  
                 : new String[0];
 424  
 
 425  0
         for (SearchConfiguration conf : context.getSearchMode().getSearchConfigurations()) {
 426  
 
 427  
             // everything on by default
 428  0
             boolean applicable = (0 == explicitCommands.length);
 429  
 
 430  
             // check for specified list of commands to run in url
 431  0
             for(String explicitCommand : explicitCommands){
 432  0
                 applicable |= explicitCommand.equalsIgnoreCase(conf.getId());
 433  
             }
 434  
 
 435  
             // check output is rss, only run the command(s) that will produce the rss output.
 436  0
             applicable &= !isRss()
 437  
                     || Arrays.asList(context.getSearchTab().getRssCommands()).contains(conf.getId().intern());
 438  
 
 439  
             // check for alwaysRun or for a possible enrichment (since its scoring will be the final indicator)
 440  0
             applicable &= conf.isAlwaysRun() ||
 441  
                     (null != context.getSearchTab().getEnrichmentByCommand(conf.getId())
 442  
                     && !datamodel.getQuery().getQuery().isBlank());
 443  
 
 444  
             // add search configuration if applicable
 445  0
             if(applicable){
 446  0
                 applicableSearchConfigurations.add(conf);
 447  
             }
 448  0
         }
 449  
 
 450  0
         return performTransformers(applicableSearchConfigurations);
 451  
     }
 452  
 
 453  
     // Private -------------------------------------------------------
 454  
 
 455  
     /** Truncates string to an acceptable length. **/
 456  
     private String truncate(final String query){
 457  
 
 458  
         // generic.sesam defines a default value of 256
 459  0
         final int length = Integer.parseInt(
 460  
                 context.getDataModel().getSite().getSiteConfiguration().getProperty("sesat.query.characterLimit"));
 461  
 
 462  0
         return length < query.length()
 463  
                 ? query.substring(0, length)
 464  
                 : query;
 465  
     }
 466  
 
 467  
     private boolean useEnrichment(
 468  
             final EnrichmentHint eHint,
 469  
             final SearchConfiguration config,
 470  
             final TokenEvaluationEngineContext tokenEvaluationEngineContext,
 471  
             final StringBuilder analysisReport){
 472  
 
 473  0
         boolean result = false;
 474  
 
 475  0
         final Map<String,StringDataObject> parameters = datamodel.getParameters().getValues();
 476  
 
 477  
         // TODO 'collapse' is not a sesat standard. standardise or move out.
 478  0
         final boolean collapse = null == parameters.get("collapse")
 479  
                 || "".equals(parameters.get("collapse").getString());
 480  
 
 481  0
         if (context.getSearchMode().isAnalysis() && collapse && eHint.getWeight() > 0){
 482  
 
 483  0
             int score = eHint.getBaseScore();
 484  
 
 485  0
             if(null != eHint.getRule()){
 486  
 
 487  0
                 final AnalysisRule rule = rules.getRule(eHint.getRule());
 488  
 
 489  0
                 if (null == scoresByRule.get(eHint.getRule())) {
 490  
 
 491  0
                     final StringBuilder analysisRuleReport = new StringBuilder();
 492  
 
 493  0
                     score += rule.evaluate(datamodel.getQuery().getQuery(),
 494  
                             ContextWrapper.wrap(
 495  
                                 AnalysisRule.Context.class,
 496  0
                                 new BaseContext(){
 497  
                                     public String getRuleName(){
 498  0
                                         return eHint.getRule();
 499  
                                     }
 500  
                                     public Appendable getReportBuffer(){
 501  0
                                         return analysisRuleReport;
 502  
                                     }
 503  
                                 },
 504  
                                 tokenEvaluationEngineContext));
 505  
 
 506  0
                     scoresByRule.put(eHint.getRule(), score);
 507  0
                     analysisReport.append(analysisRuleReport);
 508  
 
 509  0
                     LOG.debug("Score for " + config.getId() + " is " + score);
 510  
 
 511  0
                 } else {
 512  0
                     score = scoresByRule.get(eHint.getRule());
 513  
                 }
 514  
             }
 515  
 
 516  0
             scores.put(config.getId(), score);
 517  
 
 518  0
             result = score >= eHint.getThreshold();
 519  
 
 520  
         }
 521  
 
 522  0
         return config.isAlwaysRun() || result;
 523  
     }
 524  
 
 525  
     @SuppressWarnings("unchecked")
 526  
     private Map<Future<ResultList<ResultItem>>,SearchCommand> executeSearchCommands(
 527  
             final Collection<SearchCommand> commands) throws InterruptedException, TimeoutException, ExecutionException{
 528  
 
 529  0
         Map<Future<ResultList<ResultItem>>,SearchCommand> results = Collections.EMPTY_MAP;
 530  
 
 531  
         try{
 532  0
             final SearchCommandExecutor executor = SearchCommandExecutorFactory
 533  
                     .getController(context.getSearchMode().getExecutor());
 534  
 
 535  
             try{
 536  0
                 results = executor.invokeAll(commands);
 537  
 
 538  0
             }finally{
 539  
 
 540  0
                 final Map<Future<ResultList<ResultItem>>,SearchCommand> waitFor;
 541  
 
 542  0
                 if(null != datamodel.getParameters().getValue(PARAM_WAITFOR)){
 543  
 
 544  0
                     waitFor = new HashMap<Future<ResultList<ResultItem>>,SearchCommand>();
 545  
 
 546  0
                     final String[] waitForArr
 547  
                             = datamodel.getParameters().getValue(PARAM_WAITFOR).getString().split(",");
 548  
 
 549  0
                     for(String waitForStr : waitForArr){
 550  
                         // using generics on the next line crashes javac
 551  0
                         for(Entry/*<Future<ResultList<ResultItem>>,SearchCommand>*/ entry : results.entrySet()){
 552  
 
 553  0
                             final String entryName
 554  
                                     = ((SearchCommand)entry.getValue()).getSearchConfiguration().getId();
 555  
 
 556  0
                             if(waitForStr.equalsIgnoreCase(entryName)){
 557  
 
 558  0
                                 waitFor.put(
 559  
                                         (Future<ResultList<ResultItem>>)entry.getKey(),
 560  
                                         (SearchCommand)entry.getValue());
 561  0
                                 break;
 562  
                             }
 563  0
                         }
 564  
                     }
 565  
 
 566  0
                 }else if(null != datamodel.getParameters().getValue(PARAM_COMMANDS)){
 567  
 
 568  
                     // wait on everything explicitly asked for
 569  0
                     waitFor = results;
 570  
 
 571  
                 }else{
 572  
 
 573  
                     // do not wait on asynchronous commands
 574  0
                     waitFor = new HashMap<Future<ResultList<ResultItem>>,SearchCommand>();
 575  
                     // using generics on the next line crashes javac
 576  0
                     for(Entry/*<Future<ResultList<ResultItem>>,SearchCommand>*/ entry : results.entrySet()){
 577  0
                         if(!((SearchCommand)entry.getValue()).getSearchConfiguration().isAsynchronous()){
 578  
 
 579  0
                             waitFor.put(
 580  
                                     (Future<ResultList<ResultItem>>)entry.getKey(),
 581  
                                     (SearchCommand)entry.getValue());
 582  
                         }
 583  
                     }
 584  
                 }
 585  0
                 executor.waitForAll(waitFor, TIMEOUT);
 586  0
             }
 587  0
         }catch(TimeoutException te){
 588  0
             LOG.error(ERR_MODE_TIMEOUT);
 589  0
         }
 590  
 
 591  
         // Check that we have atleast one valid execution
 592  0
         for(SearchCommand command : commands){
 593  0
             allCancelled &= (null != datamodel.getParameters().getValue(PARAM_COMMANDS)
 594  
                     || !command.getSearchConfiguration().isAsynchronous());
 595  0
             allCancelled &= command.isCancelled();
 596  
         }
 597  
 
 598  0
         return results;
 599  
     }
 600  
 
 601  
     private Collection<SearchConfiguration> performTransformers(final Collection<SearchConfiguration> applicableSearchConfigurations) {
 602  0
         final RunTransformer.Context transformerContext = new RunTransformer.Context() {
 603  
                     public Collection<SearchConfiguration>getApplicableSearchConfigurations() {
 604  0
                         return applicableSearchConfigurations;
 605  
                     }
 606  
 
 607  
                     public DataModel getDataModel() {
 608  0
                         return datamodel;
 609  
                     }
 610  
 
 611  
                     public DocumentLoader newDocumentLoader(final SiteContext siteContext, final String resource, final DocumentBuilder builder) {
 612  0
                         return context.newDocumentLoader(siteContext, resource, builder);
 613  
                     }
 614  
 
 615  
                     public PropertiesLoader newPropertiesLoader(final SiteContext siteContext, final String resource, final Properties properties) {
 616  0
                         return context.newPropertiesLoader(siteContext, resource, properties);
 617  
                     }
 618  
 
 619  
                     public BytecodeLoader newBytecodeLoader(final SiteContext siteContext, final String className, final String jarFileName) {
 620  0
                         return context.newBytecodeLoader(siteContext, className, jarFileName);
 621  
                     }
 622  
 
 623  
                     public Site getSite() {
 624  0
                         return datamodel.getSite().getSite();
 625  
                     }
 626  
         };
 627  
 
 628  0
         final List<RunTransformerConfig> rtcList = context.getSearchMode().getRunTransformers();
 629  
 
 630  0
         for (final RunTransformerConfig rtc : rtcList) {
 631  0
             final RunTransformer rt = RunTransformerFactory.getController(transformerContext, rtc);
 632  0
             rt.transform(transformerContext);
 633  0
         }
 634  
 
 635  0
         return applicableSearchConfigurations;
 636  
     }
 637  
 
 638  
     private void performHandlers(){
 639  
 
 640  0
         final RunHandler.Context handlerContext = ContextWrapper.wrap(
 641  
                 RunHandler.Context.class,
 642  0
                 new SiteContext(){
 643  
                     public Site getSite() {
 644  0
                         return datamodel.getSite().getSite();
 645  
                     }
 646  
                 },
 647  
                 context);
 648  
 
 649  0
         final List<RunHandlerConfig> rhcList
 650  
                 = new ArrayList<RunHandlerConfig>(context.getSearchMode().getRunHandlers());
 651  
 
 652  
         /* Adding NavigationRunHandler to all search modes. TODO move into modes.xml */
 653  0
         rhcList.add(new NavigationRunHandlerConfig());
 654  
 
 655  0
         for (final RunHandlerConfig rhc : rhcList) {
 656  0
             final RunHandler rh = RunHandlerFactory.getController(handlerContext, rhc);
 657  
 
 658  0
             LOG.debug("executing " + rh);
 659  0
             rh.handleRunningQuery(handlerContext);
 660  0
         }
 661  0
     }
 662  
 
 663  
     private boolean isRss() {
 664  
 
 665  0
         final StringDataObject outputParam = datamodel.getParameters().getValue(PARAM_LAYOUT);
 666  0
         return null != outputParam && "rss".equals(outputParam.getString());
 667  
     }
 668  
 
 669  
     private void handleNoHits(final StringBuilder noHitsOutput, final Map<String,StringDataObject> parameters)
 670  
             throws SiteKeyedFactoryInstantiationException, InterruptedException{
 671  
 
 672  
         // there were no hits for any of the search tabs!
 673  0
         noHitsOutput.append("<absolute/>");
 674  
 
 675  0
         if( noHitsOutput.length() >0 && datamodel.getQuery().getString().length() >0
 676  
                 && !"NOCOUNT".equals(parameters.get("IGNORE"))){
 677  
 
 678  0
             final String layout = null != parameters.get(PARAM_LAYOUT)
 679  
                     ? parameters.get(PARAM_LAYOUT).getString()
 680  
                     : null;
 681  
 
 682  0
             noHitsOutput.insert(0, "<no-hits mode=\"" + context.getSearchTab().getKey()
 683  
                     + (null != layout ? "\" layout=\"" + layout : "") + "\">"
 684  
                     + "<query>" + datamodel.getQuery().getXmlEscaped() + "</query>");
 685  0
             noHitsOutput.append("</no-hits>");
 686  0
             PRODUCT_LOG.info(noHitsOutput.toString());
 687  
         }
 688  
 
 689  
         // maybe we can modify the query to broaden the search
 690  
         // replace all DefaultClause with an OrClause
 691  
         //  [simply done with wrapping the query string inside ()'s ]
 692  0
         final String queryStr = datamodel.getQuery().getString();
 693  
 
 694  0
         if (!queryStr.startsWith("(") && !queryStr.endsWith(")")
 695  
                 && datamodel.getQuery().getQuery().getTermCount() > 1
 696  
                 && context.getSearchMode().isAutoBroadening()) {
 697  
 
 698  
             // DataModel's ControlLevel will be RUNNING_QUERY_CONSTRUCTION
 699  
             //  Increment it onwards to SEARCH_COMMAND_CONSTRUCTION.
 700  0
             final DataModelFactory dataModelFactory =  DataModelFactory
 701  0
                     .instanceOf(ContextWrapper.wrap(DataModelFactory.Context.class, context, new SiteContext(){
 702  
                         public Site getSite(){
 703  0
                             return datamodel.getSite().getSite();
 704  
                         }
 705  
                     }));
 706  0
             dataModelFactory.assignControlLevel(datamodel, ControlLevel.RUNNING_QUERY_CONSTRUCTION);
 707  
 
 708  
             // create and run a new RunningQueryImpl
 709  0
             new RunningQueryImpl(context, '(' + queryStr + ')').run();
 710  
 
 711  
             // TODO put in some sort of feedback to user that query has been changed.
 712  
         }
 713  
 
 714  0
     }
 715  
 
 716  
     // Inner classes -------------------------------------------------
 717  
 }