// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.prelude.templates; import com.yahoo.io.ByteWriter; import com.yahoo.prelude.fastsearch.FastHit; import com.yahoo.prelude.fastsearch.XMLField; import com.yahoo.search.Result; import com.yahoo.text.Utf8String; import com.yahoo.text.XMLWriter; import java.io.IOException; import java.io.Writer; import java.util.logging.Logger; /** * A wrapper for a template set, suitable for subclassing. * *
* A subclass of UserTemplate must implement header(), footer(), hit(),
* hitFooter(), error() and noHits().
*
* @deprecated use a renderer instead
* @author Steinar Knutsen
*/
@SuppressWarnings("deprecation")
@Deprecated // OK (But wait for deprecated handlers in vespaclient-container-plugin to be removed)
// TODO: Remove on Vespa 7
public abstract class UserTemplate
* The result set is available in the context object under the name
* "result".
*
* @param context
* wrapper which will contain, among other thing, the result
* set instance
* @param writer
* the destination for rendering the result
* @throws IOException
* may be propagated from the writer
*/
public abstract void header(Context context, T writer)
throws IOException;
/**
* Render the result set footer.
*
*
* The result set is available in the context object under the name
* "result".
*
* @param context
* wrapper which will contain, among other thing, the result
* set instance
* @param writer
* the destination for rendering the result
* @throws IOException
* may be propagated from the writer
*/
public abstract void footer(Context context, T writer)
throws IOException;
/**
* Render a single top level hit.
*
*
* The result set is available in the context object under the name
* "result". The hit itself as "hit", the index of the hit as "hitno", and
* all the fields under their normal names.
*
* @param context
* wrapper which will contain, among other thing, the hit
* instance
* @param writer
* the destination for rendering the hit
* @throws IOException
* may be propagated from the writer
*/
public abstract void hit(Context context, T writer) throws IOException;
/**
* Render a footer for a single top level hit. A typical implementation may
* do nothing.
*
*
* The result set is available in the context object under the name
* "result". The hit itself as "hit", the index of the hit as "hitno", and
* all the fields under their normal names.
*
* @param context
* wrapper which will contain, among other thing, the hit
* instance
* @param writer
* the destination for rendering the hit
* @throws IOException
* may be propagated from the writer
*/
public abstract void hitFooter(Context context, T writer)
throws IOException;
/**
* Render the error message for a result set.
*
*
* The result set is available in the context object under the name
* "result".
*
* @param context
* wrapper which will contain, among other thing, main error
* and result set instances.
* @param writer
* the destination for rendering the hit
* @throws IOException
* may be propagated from the writer
*/
public abstract void error(Context context, T writer)
throws IOException;
/**
* Invoked when the result set has no hits.
*
*
* The result set is available in the context object under the name
* "result".
*
* @param context
* wrapper which will contain, among other thing, the result
* set instance
* @param writer
* the destination for rendering the hit
* @throws IOException
* may be propagated from the writer
*/
public abstract void noHits(Context context, T writer)
throws IOException;
/**
* Override this to add custom rendering for the query context of the result.
* Only called when the query context is present.
*
*
* The result set is available in the context object under the name
* "result". The query context is retrieved from the result by calling
* result.getQuery.getContext(false)
*
* @param context
* wrapper which will contain, among other things, the result
* set instance
* @param writer
* the destination for rendering the hit
* @throws IOException
* may be propagated from the writer
*/
public void queryContext(Context context, T writer) throws IOException {
Result result = (Result) context.get("result");
result.getContext(false).render(writer);
}
/**
* Dump UTF-8 byte array to writer, but escape low ASCII codes except
* TAB, NL and CR, and escape ampersand, less than and greater than.
*
*
* It is presumed the writer is buffered (which is the case in normal
* result rendering), as the method may perform a large number of write
* operations.
*
*
* public only for testing.
*/
public static void dumpAndXMLQuoteUTF8(ByteWriter writer, byte[] utf) throws java.io.IOException {
int startDump = 0;
for (int i = 0; i < utf.length; ++i) {
byte b = utf[i];
if (b < 0) {
// Not ASCII, above character 127
// Don't try to do something smart with UNICODE characters,
// just pass them through.
} else if (b < 32) {
switch (b) {
case 9:
case 10:
case 13:
break;
default:
writer.append(utf, startDump, i - startDump);
startDump = i + 1;
quoteByte(writer, b);
break;
}
} else {
// printable ASCII
// quote special characters, otherwise do nothing
switch (b) {
// case 34: // double quote
// writer.append(utf, startDump, i - startDump);
// startDump = i + 1;
// writer.append(doubleQuote);
// break;
case 38: // ampersand
writer.append(utf, startDump, i - startDump);
startDump = i + 1;
writer.append(ampersand);
break;
case 60: // less than
writer.append(utf, startDump, i - startDump);
startDump = i + 1;
writer.append(lessThan);
break;
case 62: // greater than
writer.append(utf, startDump, i - startDump);
startDump = i + 1;
writer.append(greaterThan);
break;
}
}
}
if (startDump < utf.length) {
writer.append(utf, startDump, utf.length - startDump);
}
}
/**
* If the field is available as a UTF-8 byte array,
* dump it to the writer.
*/
public static boolean dumpBytes(ByteWriter writer,
FastHit hit,
String fieldName) throws java.io.IOException {
return false;
}
private static void quoteByte(ByteWriter writer, byte b) throws java.io.IOException {
byte[] quoted = new byte[2];
writer.append(quotePrefix);
quoted[0] = (byte) ((b >>> 4) + 0x30);
if (quoted[0] > 0x39) {
quoted[0] = (byte) (quoted[0] + 7);
}
quoted[1] = (byte) ((b & 0x0f) + 0x30);
if (quoted[1] > 0x39) {
quoted[1] = (byte) (quoted[1] + 7);
}
writer.append(quoted);
}
}