Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
266 lines (230 sloc) 9.41 KB
---
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
title: "Search Result Renderers"
---
<p>
Vespa provides a default JSON format for queryresults.
If a separate format is desired, <i>renderers</i> can be configured
to implement custom formats. Both binary and text format may be implemented in a renderer.
Renderers should not be used to implement business logic - that should go in
Searchers, Handlers or Processors.
</p><p>
The documentation of renderers assumes familiarity with
<a href="jdisc/developing-applications.html">component development</a>.
</p>
<p>
Renderers are implemented by subclassing <a
href="http://javadoc.io/page/com.yahoo.vespa/container-core/latest/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.html">com.yahoo.processing.rendering.AsynchronousSectionedRenderer&lt;Result&gt;</a>
or <a
href="http://javadoc.io/page/com.yahoo.vespa/container-search/latest/com/yahoo/search/rendering/Renderer.html">com.yahoo.search.rendering.Renderer</a>
or <a
href="http://javadoc.io/page/com.yahoo.vespa/container-search/latest/com/yahoo/search/rendering/SectionedRenderer.html">com.yahoo.search.rendering.SectionedRenderer</a>.
SectionedRenderer differs from Renderer by providing each part to be rendered in
separate steps. It is therefore easier to implement a SectionedRenderer than a
regular Renderer. AsynchronousSectionedRenderer has a similar API to
SectionedRenderer, but supports asynchronously fetched hit contents, so if
supporting slow clients or backends is a priority, this offers some advantages.
AsynchronousSectionedRenderer also exposes an OutputStream instead of a Writer,
so if the backend data contains e.g. data encoded the same way as the output
from the container (often UTF-8), notable performance gains are possible.
</p><p>
All renderers are components; They are built and deployed like all
other search container components, and
supports <a href="configuring-components.html">custom config.</a>
</p><p>
Renderers do <em>not</em> need to be thread safe; They can safely use
and store state during rendering in member variables. The container
supports this by cloning the renderers just before rendering the
search result. To support cloning correctly, the renderers are
required to obey the following contract:
</p>
<ol>
<li>At construction time, only final members shall be initialized,
and these must refer to immutable data only. </li>
<li>State mutated during rendering shall be initialized in the init
method.</li>
</ol>
<h2 id="sectionedrenderer">SectionedRenderer</h2>
<p>
To create a SectionedRenderer, you need to subclass it and implement
all its abstract methods. For each non-compound entity such as
regular hits and query contexts there are an associated method with
the same name.
<pre>
public class DemoRenderer extends SectionedRenderer&lt;Writer&gt; {
@Override
public void hit(Writer writer, Hit hit) throws IOException {
writer.write("Hit: " + hit.getField("documentid") + "\n");
}
}
</pre>
For each compound entity such as hit groups and the result itself
there are pairs of methods, named begin&lt;name&gt; and
end&lt;name&gt;.
<pre>
public class DemoRenderer extends SectionedRenderer&lt;Writer&gt; {
private int indentation;
@Override
public void beginHitGroup(PrintWriter writer, HitGroup hitGroup) throws IOException {
writer.write("Begin hit group:" + hitGroup.getId() + "\n");
++indentation;
}
@Override
public void endHitGroup(PrintWriter writer, HitGroup hitGroup) throws IOException {
--indentation;
writer.write("End hit group:" + hitGroup.getId() + "\n");
}
}
</pre>
For a compound entity, a method will be called for each of its members
after its begin method and before its end method has been called.
<pre>
Sequence of calls
-------------------
Result { 1. beginResult()
HitGroup { 2. beginHitGroup()
Hit 3. hit()
Hit 4. hit()
Hit 5. hit()
} 6. endHitGroup()
} 7. endResult()
</pre>
For <a href="grouping.html">grouping results</a>, there is a dedicated set of callbacks available;
<tt>beginGroup()</tt>, <tt>endGroup()</tt>, <tt>beginGroupList()</tt>, <tt>endGroupList()</tt>,
<tt>beginHitList()</tt> and <tt>endHitList()</tt>.
</p><p class="note">
All of <tt>GroupList</tt>, <tt>Group</tt> and <tt>HitList</tt> are subclasses of <tt>HitGroup</tt>, and the default
implementation of the above methods is provided that calls <tt>beginHitGroup()</tt> and <tt>endHitGroup()</tt>
respectively. Furthermore, since all of the attributes of those classes are regular fields as defined by the
root <tt>Hit</tt> class, you will get reasonable output by simply implementing <tt>beginHitGroup()</tt>,
<tt>endHitGroup()</tt>, and <tt>hit()</tt>.
</p>
<h2 id="json-example">JSON example</h2>
<p>
JSON is the default output format. Read the
<a href="reference/default-result-format.html">default JSON result format</a>
before implementing custom JSON renderers.
</p><p>
The following example renders a set of fields containing JSON data as a JSON array:
<pre>
package com.yahoo.mysearcher;
import com.yahoo.search.Result;
import com.yahoo.search.query.context.QueryContext;
import com.yahoo.search.rendering.SectionedRenderer;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
public class MyRenderer extends SectionedRenderer&lt;Writer&gt; {
/**
* A marker variable for the hit rendering to know whether
* the hit being rendered is the first one that is rendered.
*/
boolean firstHit;
public void init() {
firstHit = true;
}
@Override
public String getEncoding() {
return "utf-8";
}
@Override
public String getMimeType() {
return "application/json";
}
@Override
public void beginResult(Writer writer, Result result) throws IOException {
writer.write("[");
}
@Override
public void endResult(Writer writer, Result result) throws IOException {
writer.write("]");
}
@Override
public void error(Writer writer, Collection&lt;ErrorMessage&gt; errorMessages) throws IOException {
// swallows errors silently
}
@Override
public void emptyResult(Writer writer, Result result) throws IOException {
//write nothing.
}
@Override
public void queryContext(Writer writer, QueryContext queryContext) throws IOException {
//write nothing.
}
@Override
public void beginHitGroup(Writer writer, HitGroup hitGroup) throws IOException {
//write nothing.
}
@Override
public void endHitGroup(Writer writer, HitGroup hitGroup) throws IOException {
//write nothing.
}
@Override
public void hit(Writer writer, Hit hit) throws IOException {
if (!firstHit) {
writer.write(",\n");
}
writer.write(hit.toString());
firstHit = false;
}
}
</pre>
In other words, dump a variable length array containing all available
data, ignore everything else and silently ignore error states. In
other words, this is not suitable for production, but it is enough for a prototype.
</p><p>
To make the above renderer available, add to <em>services.xml</em>:
<pre>
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;services version="1.0"&gt;
&hellip;
&lt;container version="1.0"&gt;
&lt;search&gt;
<span style="background-color: yellow;">&lt;renderer id="MyRenderer" class="com.yahoo.mysearcher.MyRenderer" bundle="mybundle" /&gt;</span>
&lt;/search&gt;
&hellip;
&lt;/container&gt;
&hellip;
&lt;/services&gt;
</pre>
To use the renderer, add <em>&amp;presentation.format=[id]</em> to queries -
in this case <em>&amp;presentation.format=MyRenderer</em>
</p><p>
The renderer itself needs to be distributed to the container nodes as an OSGi bundle.
The steps for creating a valid bundle for this use, are the same as for
<a href="jdisc/developing-applications.html">building any other component bundle</a>.
</p>
<h2 id="renderer">Renderer</h2>
<p>
SectionedRenderers are feed each part of the result separately.
But sometimes this scaffolding is unnecessary; you just want to pull out
the parts of the results yourself.
<pre>
public class SimpleRenderer extends Renderer {
@Override
public void render(Writer writer, Result result) throws IOException {
writer.write("The result contains " + result.getHitCount() + " hits.");
}
@Override
public String getEncoding() {
return "utf-8";
}
@Override
public String getMimeType() {
return "text/plain";
}
}
</pre>
Here, the render method is expected to do all the work; the derived
class is expected to extract all the entities of interest itself and render them.
</p>
<h2 id="asynchronoussectionedrenderer">AsynchronousSectionedRenderer&lt;Result&gt;</h2>
<p>
This is exactly the same as for the <a href="jdisc/processing.html#response-rendering">processing framework</a>.
It is conceptually very similar to SectionedRenderer, but has no special
cases for search results as such. The utility method getResponse() has a parametrized return type, though,
so templating the renderer on <code>Result</code> takes away some of the hassle.
</p>