Funnelback logo

Documentation

CATEGORY

User interface hook scripts

Introduction

The Modern UI allows you to insert hook scripts at certain stages of the search process. These hook scripts allow you to perform actions over the input parameters before the query is submitted to the query processor or transform the results after the query has been run but before the results are displayed to the user.

The hook scripts must be written in the Groovy programming language. Groovy runs on top of the Java Virtual Machine and offers the same power of Java classes but with more flexibility. The hook scripts will be automatically re-compiled when a change is detected in them.

Search process / lifecycle

Search-lifecycle.png

The Modern UI provides 4 hook points where scripts can be inserted in the search lifecycle. The search lifecycle runs the following phases:

  1. A query is submitted on a specific collection and passes an input processing phase. This phase will configure any relevant parameter depending on the collection configuration, apply Faceted Navigation constraints, transform some meta_* or query_* parameters into query expressions, etc.
  2. Once the input parameters have been prepared the search results are fetched. The query processor is run with the input parameters and return results.
  3. The results then passes an output processing phase. This phase will transform the result data according to the configuration, apply click tracking URLs, generate Faceted Navigation data from the metadata counts, etc.
  4. Once the results have been transformed and the Data Model is ready, it's passed to FreeMarker which will render the search results page.

The hook scripts can be placed:

  • At the very beginning of the lifecycle, before the input phase. This hook is called pre_process
  • Just before the query processor is run, but after the input phase: This hook is called pre_datafetch
  • Just after the query processor is run, but before the output phase: This hook is called post_datafetch
  • At the very end of the process, just before the search results page is generated: This hook is called post_process


Creating hook scripts

The hook scripts for a collection must be created in the collection configuration directory (they can be created using the file-manager). The name of the files starts with hook_ and uses the name of the hooks previously mentioned. It's suffixed with .groovy.

You can create either 1, 2, 3 or the 4 possible hooks scripts for a collection:

  • $SEARCH_HOME/conf/[collection]/hook_pre_process.groovy
  • $SEARCH_HOME/conf/[collection]/hook_pre_datafetch.groovy
  • $SEARCH_HOME/conf/[collection]/hook_post_datafetch.groovy
  • $SEARCH_HOME/conf/[collection]/hook_post_process.groovy

Hook scripts immediately take effect as soon as they're present in a collection's configuration directory. Any changes in them will be immediately taken into account.

Writing hook scripts

The hook scripts have access to the same Data Model as the form files. If you're already comfortable with writing search forms for the Modern UI using FreeMarker, writing hook scripts shouldn't be difficult.

The only difference reside in accessing the root data model objects like question and response. While they're directly available as is from a FreeMarker template, in hook scripts they're grouped under a transaction root object. So for example if you need to access the ID of the collection being searched you must use:

transaction.question.collection.id

The hook scripts doesn't need to implement a specific interface or to have a specific header. You can start writing the body of your script within the first line of the .groovy file. For example the following 1-line hook script appends "cat" to the user submitted query:

transaction.question.query += " cat"

Debugging hook scripts

Compilation problems

If your script doesn't compile for any reason the error will be logged in the Modern UI log file: $SEARCH_HOME/web/logs/modernui.log.

The most common compilation problems are:

  • Syntax errors.
  • Typos when accessing the Data Model, such as using transaction.question.colection instead of transaction.question.collection.

Message logging

If you need to log messages from within your hook script you can do so by using an logger object. A logger object has a name and can log messages with various severity levels (debug, warn, info, error or fatal). To use a logger object from within your script use the following code:

def logger = org.apache.commons.logging.LogFactory.getLog("MyHookScript")
logger.debug("The query is: " + transaction.question.query");
...
logger.fatal("No results were found")

The log messages will be written in the Modern UI log file: $SEARCH_HOME/web/logs/modernui.log.

Note: The default configuration of log messages is set to output the error level of above, except for the loggers belonging to the com.funnelback namespace which output info messages. That mean that your messages won't appear unless:

  • You use logger.error() or logger.fatal()
  • You use logger.info() and your logger belongs to the Funnelback namespace: def logger = org.apache.commons.logging.LogFactory.getLog("com.funnelback.MyHookScript")

The logging configuration and levels for the Modern UI can be edited in $SEARCH_HOME/web/conf/modernui/log4j.properties.

Examples

Please consult the Data Model documentation if you're not familiar with the data model contents (question, response, resultPacket, etc.).

Transforming each results

This example iterates over each result and changes the host name on each live URL using a regular expression. This is a post_datafetch hook because it should happen before the URLs are updated with click tracking information during the output phase.

if ( transaction.response != null
  && transaction.response.resultPacket != null) {
  transaction.response.resultPacket.results.each() {
    // In Groovy, "it" represents the item being iterated
    it.liveUrl = (it.liveUrl =~ /www.badhost.com/).replaceAll("www.correcthost.com")
  }
}

Processing additional input parameters

This example takes a country query string parameter and applies a query constraint on the p metadata class if it exists. Calling http://server.com/s/search.html?collection=test&query=travel&country=Australia will result in the query travel |p:"Australia" being run.

It's a pre_process hook because it should be run before the meta_* parameters are transformed to query expressions during the input phase.

def logger = org.apache.commons.logging.LogFactory.getLog("com.funnelback.hooks.CountryParamHook")
def q = transaction.question  
// Set the input parameter 'meta_p_phrase_sand' to the value
// of the 'country' query string parameter, if it exist
if (q.inputParameterMap["country"] != null) {
  q.inputParameterMap["meta_p_phrase_sand"] = q.inputParameterMap["country"]
  logger.info("Applied country constraint: " + q.inputParameterMap["country"])
}

Alternatively it could be done as a pre_datafetch hook by providing directly a query expression:

if (q.inputParameterMap["country"] != null) {
  q.metaParameters.add("|p:\"" + q.inputParameterMap["country"] + "\"")
}

Transforming results metadata

This script takes the value of the d and t metadata, concatenates them and put them in the x metadata, for each result. It can be either a post_datafetch or a post_process hook since it doesn't depend of any transformation done in the output phase.

if ( transaction.response != null
  && transaction.response.resultPacket != null) {
    transaction.response.resultPacket.results.each() {
      // In Groovy, "it" represents the item being iterated
      if (it.metaData["d"] != null && it.metaData["t"] != null) {
        it.metaData["x"] = "Document " + it.metaData["t"] + " created on: " + it.metaData["t"]
      }
   }
}

top ⇑