Vespa provides a default JSON format for query results. Renderers can be configured to implement custom formats, like binary and text format. Renderers should not be used to implement business logic - that should go in Searchers, Handlers or Processors. This guide assumes familiarity with the Developer Guide.
Renderers are implemented by subclassing one of:
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 data encoded the same way as the output from the container (often UTF-8), performance gains are possible.
All renderers are components. They are built and deployed like all other container components, and supports custom config.
Renderers do not 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:
To enable a renderer, add to services.xml:
<?xml version="1.0" encoding="utf-8" ?>
<services version="1.0">
…
<container version="1.0">
<search>
<renderer id="MyRenderer"
class="com.yahoo.mysearcher.MyRenderer"
bundle="the name in <artifactId> in pom.xml" />
</search>
…
</container>
…
</services>
To use the renderer, add &presentation.format=[id]
to queries - in this case &presentation.format=MyRenderer
.
The simplest form of a renderer is extending Renderer
.
The render
method does all the work -
the derived class is expected to extract all the entities of interest itself and render them.
Simple example:
More complex example:
To create a SectionedRenderer, 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:
For each compound entity, such as hit groups and the result itself,
there are pairs of methods, named begin<name>
and end<name>
:
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:
Call sequence ------------------- Result { 1. beginResult() HitGroup { 2. beginHitGroup() Hit 3. hit() Hit 4. hit() Hit 5. hit() } 6. endHitGroup() } 7. endResult()
For grouping results, there is a dedicated set of callbacks available:
beginGroup()
/ endGroup()
beginGroupList()
/ endGroupList()
beginHitList()
/ endHitList()
All of Group
, GroupList
and HitList
are subclasses of HitGroup
,
and the default implementation of the above methods is provided that calls
beginHitGroup()
and endHitGroup()
, respectively.
Furthermore, since all of the attributes of those classes are regular fields
as defined by the root Hit
class,
output is made by simply implementing beginHitGroup()
,
endHitGroup()
, and hit()
.
Read the default JSON result format before implementing custom JSON renderers. Example: Render a set of fields containing JSON data as a JSON array. In other words, dump a variable length array containing all available data, ignore everything else and silently ignore error states (i.e. good for prototyping):
This is the same as for the processing framework.
It is conceptually 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 Result
takes away some of the hassle.
Find an example in DemoRenderer.java.