diff options
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/rendering')
4 files changed, 14 insertions, 463 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/DefaultRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/DefaultRenderer.java deleted file mode 100644 index 30695338741..00000000000 --- a/container-search/src/main/java/com/yahoo/search/rendering/DefaultRenderer.java +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.search.rendering; - -import com.yahoo.concurrent.CopyOnWriteHashMap; -import com.yahoo.data.XmlProducer; -import com.yahoo.io.ByteWriter; -import com.yahoo.net.URI; -import com.yahoo.prelude.fastsearch.GroupingListHit; -import com.yahoo.prelude.hitfield.HitField; -import com.yahoo.prelude.hitfield.JSONString; -import com.yahoo.prelude.hitfield.XMLString; -import com.yahoo.processing.rendering.AsynchronousSectionedRenderer; -import com.yahoo.processing.response.Data; -import com.yahoo.processing.response.DataList; -import com.yahoo.search.Query; -import com.yahoo.search.Result; -import com.yahoo.search.grouping.result.HitRenderer; -import com.yahoo.search.query.context.QueryContext; -import com.yahoo.search.result.*; -import com.yahoo.text.Utf8String; -import com.yahoo.text.XML; -import com.yahoo.text.XMLWriter; -import com.yahoo.yolean.trace.TraceNode; -import com.yahoo.yolean.trace.TraceVisitor; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.util.Iterator; -import java.util.concurrent.Executor; -import java.util.stream.Collectors; - -/** - * XML rendering of search results. This is NOT the default (but it once was). - * - * @author Tony Vaagenes - * @deprecated use JsonRenderer instead - */ -@SuppressWarnings({ "rawtypes", "deprecation" }) -@Deprecated // OK -// TODO: Rename to XmlRenderer on Vespa 7 -public final class DefaultRenderer extends AsynchronousSectionedRenderer<Result> { - - public static final String DEFAULT_MIMETYPE = "text/xml"; - public static final String DEFAULT_ENCODING = "utf-8"; - - private static final Utf8String RESULT = new Utf8String("result"); - private static final Utf8String GROUP = new Utf8String("group"); - private static final Utf8String ID = new Utf8String("id"); - private static final Utf8String FIELD = new Utf8String("field"); - private static final Utf8String HIT = new Utf8String("hit"); - private static final Utf8String ERROR = new Utf8String("error"); - private static final Utf8String TOTAL_HIT_COUNT = new Utf8String("total-hit-count"); - private static final Utf8String QUERY_TIME = new Utf8String("querytime"); - private static final Utf8String SUMMARY_FETCH_TIME = new Utf8String("summaryfetchtime"); - private static final Utf8String SEARCH_TIME = new Utf8String("searchtime"); - private static final Utf8String NAME = new Utf8String("name"); - private static final Utf8String CODE = new Utf8String("code"); - private static final Utf8String COVERAGE_DOCS = new Utf8String("coverage-docs"); - private static final Utf8String COVERAGE_NODES = new Utf8String("coverage-nodes"); - private static final Utf8String COVERAGE_FULL = new Utf8String("coverage-full"); - private static final Utf8String COVERAGE = new Utf8String("coverage"); - private static final Utf8String RESULTS_FULL = new Utf8String("results-full"); - private static final Utf8String RESULTS = new Utf8String("results"); - private static final Utf8String TYPE = new Utf8String("type"); - private static final Utf8String RELEVANCY = new Utf8String("relevancy"); - private static final Utf8String SOURCE = new Utf8String("source"); - - - // this is shared between umpteen threads by design - private final CopyOnWriteHashMap<String, Utf8String> fieldNameMap = new CopyOnWriteHashMap<>(); - - private XMLWriter writer; - - public DefaultRenderer() { - this(null); - } - - /** - * Creates an XML renderer using a custom executor. - * Using a custom executor is useful for tests to avoid creating new threads for each renderer registry. - */ - public DefaultRenderer(Executor executor) { - super(executor); - } - - @Override - public void init() { - super.init(); - writer = null; - } - - @Override - public String getEncoding() { - if (getResult() == null - || getResult().getQuery() == null - || getResult().getQuery().getModel().getEncoding() == null) { - return DEFAULT_ENCODING; - } else { - return getResult().getQuery().getModel().getEncoding(); - } - } - - @Override - public String getMimeType() { - return DEFAULT_MIMETYPE; - } - - private XMLWriter wrapWriter(Writer writer) { - return XMLWriter.from(writer, 10, -1); - } - - private void header(XMLWriter writer, Result result) throws IOException { - // TODO: move setting this to Result - writer.xmlHeader(getRequestedEncoding(result.getQuery())); - writer.openTag(RESULT).attribute(TOTAL_HIT_COUNT, String.valueOf(result.getTotalHitCount())); - renderCoverageAttributes(result.getCoverage(false), writer); - renderTime(writer, result); - writer.closeStartTag(); - } - - private void renderTime(XMLWriter writer, Result result) { - if ( ! result.getQuery().getPresentation().getTiming()) return; - - final String threeDecimals = "%.3f"; - final double milli = .001d; - final long now = System.currentTimeMillis(); - final long searchTime = now - result.getElapsedTime().first(); - final double searchSeconds = ((double) searchTime) * milli; - - if (result.getElapsedTime().firstFill() != 0L) { - final long queryTime = result.getElapsedTime().weightedSearchTime(); - final long summaryFetchTime = result.getElapsedTime().weightedFillTime(); - final double querySeconds = ((double) queryTime) * milli; - final double summarySeconds = ((double) summaryFetchTime) * milli; - writer.attribute(QUERY_TIME, String.format(threeDecimals, querySeconds)); - writer.attribute(SUMMARY_FETCH_TIME, String.format(threeDecimals, summarySeconds)); - } - writer.attribute(SEARCH_TIME, String.format(threeDecimals, searchSeconds)); - } - - protected static void renderCoverageAttributes(Coverage coverage, XMLWriter writer) throws IOException { - if (coverage == null) return; - writer.attribute(COVERAGE_DOCS,coverage.getDocs()); - writer.attribute(COVERAGE_NODES,coverage.getNodes()); - writer.attribute(COVERAGE_FULL,coverage.getFull()); - writer.attribute(COVERAGE,coverage.getResultPercentage()); - writer.attribute(RESULTS_FULL,coverage.getFullResultSets()); - writer.attribute(RESULTS,coverage.getResultSets()); - } - - public void error(XMLWriter writer, Result result) throws IOException { - ErrorMessage error = result.hits().getError(); - writer.openTag(ERROR).attribute(CODE,error.getCode()).content(error.getMessage(),false).closeTag(); - } - - @SuppressWarnings("UnusedParameters") - protected void emptyResult(XMLWriter writer, Result result) throws IOException {} - - @SuppressWarnings("UnusedParameters") - public void queryContext(XMLWriter writer, QueryContext queryContext, Query owner) throws IOException { - if (owner.getTraceLevel()!=0) { - XMLWriter xmlWriter=XMLWriter.from(writer); - xmlWriter.openTag("meta").attribute("type", QueryContext.ID); - TraceNode traceRoot = owner.getModel().getExecution().trace().traceNode().root(); - traceRoot.accept(new RenderingVisitor(xmlWriter, owner.getStartTime())); - xmlWriter.closeTag(); - } - } - - private void renderSingularHit(XMLWriter writer, Hit hit) { - writer.openTag(HIT); - renderHitAttributes(writer, hit); - writer.closeStartTag(); - renderHitFields(writer, hit); - } - - private void renderHitFields(XMLWriter writer, Hit hit) { - renderSyntheticRelevanceField(writer, hit); - hit.forEachField((name, value) -> renderField(writer, name, value)); - } - - private void renderField(XMLWriter writer, String name, Object value) { - if (name.startsWith("$")) return; - - writeOpenFieldElement(writer, name); - renderFieldContent(writer, value); - writeCloseFieldElement(writer); - } - - private void renderFieldContent(XMLWriter writer, Object value) { - writer.escapedContent(asXML(value), false); - } - - private String asXML(Object value) { - if (value == null) - return "(null)"; - else if (value instanceof XmlProducer) - return ((XmlProducer)value).toXML(); - else if (value instanceof HitField) - return ((HitField)value).quotedContent(false); - else if (value instanceof StructuredData || value instanceof XMLString || value instanceof JSONString) - return value.toString(); - else - return XML.xmlEscape(value.toString(), false, '\u001f'); - } - - private void renderSyntheticRelevanceField(XMLWriter writer, Hit hit) { - String relevancyFieldName = "relevancy"; - Relevance relevance = hit.getRelevance(); - - // skip depending on hit type - if (relevance != null) { - renderSimpleField(writer, relevancyFieldName, relevance); - } - } - - private void renderSimpleField(XMLWriter writer, String relevancyFieldName, Relevance relevance) { - writeOpenFieldElement(writer, relevancyFieldName); - writer.content(relevance.toString(), false); - writeCloseFieldElement(writer); - } - - private void writeCloseFieldElement(XMLWriter writer) { - writer.closeTag(); - } - - private void writeOpenFieldElement(XMLWriter writer, String relevancyFieldName) { - Utf8String utf8 = fieldNameMap.get(relevancyFieldName); - if (utf8 == null) { - utf8 = new Utf8String(relevancyFieldName); - fieldNameMap.put(relevancyFieldName, utf8); - } - writer.openTag(FIELD).attribute(NAME, utf8); - writer.closeStartTag(); - } - - private void renderHitAttributes(XMLWriter writer, Hit hit) { - writer.attribute(TYPE, hit.types().stream().collect(Collectors.joining(" "))); - if (hit.getRelevance() != null) - writer.attribute(RELEVANCY, hit.getRelevance().toString()); - writer.attribute(SOURCE, hit.getSource()); - } - - private void renderHitGroup(XMLWriter writer, HitGroup hit) throws IOException { - if (HitRenderer.renderHeader(hit, writer)) { - // empty - } else if (hit.types().contains("grouphit")) { - // TODO Keep this? - renderHitGroupOfTypeGroupHit(writer, hit); - } else { - renderGroup(writer, hit); - } - } - - private void renderGroup(XMLWriter writer, HitGroup hit) { - writer.openTag(GROUP); - renderHitAttributes(writer, hit); - writer.closeStartTag(); - } - - private void renderHitGroupOfTypeGroupHit(XMLWriter writer, HitGroup hit) { - writer.openTag(HIT); - renderHitAttributes(writer, hit); - renderId(writer, hit); - writer.closeStartTag(); - } - - private void renderId(XMLWriter writer, HitGroup hit) { - URI uri = hit.getId(); - if (uri != null) { - writer.openTag(ID).content(uri.stringValue(),false).closeTag(); - } - } - - private boolean simpleRenderHit(XMLWriter writer, Hit hit) throws IOException { - if (hit instanceof DefaultErrorHit) { - return simpleRenderDefaultErrorHit(writer, (DefaultErrorHit) hit); - } else if (hit instanceof GroupingListHit) { - return true; - } else { - return false; - } - } - - public static boolean simpleRenderDefaultErrorHit(XMLWriter writer, ErrorHit defaultErrorHit) throws IOException { - writer.openTag("errordetails"); - for (Iterator i = defaultErrorHit.errorIterator(); i.hasNext();) { - ErrorMessage error = (ErrorMessage) i.next(); - renderMessageDefaultErrorHit(writer, error); - } - writer.closeTag(); - return true; - } - - public static void renderMessageDefaultErrorHit(XMLWriter writer, ErrorMessage error) throws IOException { - writer.openTag("error"); - writer.attribute("source", error.getSource()); - writer.attribute("error", error.getMessage()); - writer.attribute("code", Integer.toString(error.getCode())); - writer.content(error.getDetailedMessage(), false); - if (error.getCause()!=null) { - writer.openTag("cause"); - writer.content("\n", true); - StringWriter stackTrace=new StringWriter(); - error.getCause().printStackTrace(new PrintWriter(stackTrace)); - writer.content(stackTrace.toString(), true); - writer.closeTag(); - } - writer.closeTag(); - } - - public static final class RenderingVisitor extends TraceVisitor { - - private static final String tag = "p"; - private final XMLWriter writer; - private long baseTime; - - public RenderingVisitor(XMLWriter writer,long baseTime) { - this.writer=writer; - this.baseTime=baseTime; - } - - @Override - public void entering(TraceNode node) { - if (node.isRoot()) return; - writer.openTag(tag); - } - - @Override - public void leaving(TraceNode node) { - if (node.isRoot()) return; - writer.closeTag(); - } - - @Override - public void visit(TraceNode node) { - if (node.isRoot()) return; - if (node.payload()==null) return; - - writer.openTag(tag); - if (node.timestamp()!=0) - writer.content(node.timestamp()-baseTime,false).content(" ms: ", false); - writer.content(node.payload().toString(),false); - writer.closeTag(); - } - - } - - private Result getResult() { - Result r; - try { - r = (Result) getResponse(); - } catch (ClassCastException e) { - throw new IllegalArgumentException( - "DefaultRenderer attempted used outside a search context, got a " - + getResponse().getClass().getName()); - } - return r; - } - - @Override - public void beginResponse(OutputStream stream) throws IOException { - Charset cs = Charset.forName(getRequestedEncoding(getResult().getQuery())); - CharsetEncoder encoder = cs.newEncoder(); - writer = wrapWriter(new ByteWriter(stream, encoder)); - - header(writer, getResult()); - if (getResult().hits().getError() != null || getResult().hits().getQuery().errors().size() > 0) { - error(writer, getResult()); - } - - if (getResult().getConcreteHitCount() == 0) { - emptyResult(writer, getResult()); - } - - if (getResult().getContext(false) != null) { - queryContext(writer, getResult().getContext(false), getResult().getQuery()); - } - - } - - /** Returns the encoding of the query, or the encoding given by the template if none is set */ - public final String getRequestedEncoding(Query query) { - String encoding = query.getModel().getEncoding(); - if (encoding != null) return encoding; - return getEncoding(); - } - - @Override - public void beginList(DataList<?> list) throws IOException { - if (getRecursionLevel() == 1) return; - - HitGroup hit = (HitGroup) list; - boolean renderedSimple = simpleRenderHit(writer, hit); - if (renderedSimple) return; - - renderHitGroup(writer, hit); - } - - @Override - public void data(Data data) throws IOException { - Hit hit = (Hit) data; - boolean renderedSimple = simpleRenderHit(writer, hit); - if (renderedSimple) return; - - renderSingularHit(writer, hit); - writer.closeTag(); - } - - @Override - public void endList(DataList<?> list) { - if (getRecursionLevel() > 1) - writer.closeTag(); - } - - @Override - public void endResponse() { - writer.closeTag(); - writer.close(); - } - -} diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java index 7d60d7cf9ee..e8af150ce25 100644 --- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java +++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java @@ -18,6 +18,7 @@ import com.yahoo.document.datatypes.StringFieldValue; import com.yahoo.document.datatypes.TensorFieldValue; import com.yahoo.document.json.JsonWriter; import com.yahoo.lang.MutableBoolean; +import com.yahoo.prelude.hitfield.HitField; import com.yahoo.processing.Response; import com.yahoo.processing.execution.Execution.Trace; import com.yahoo.processing.rendering.AsynchronousSectionedRenderer; @@ -473,7 +474,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> { generator.writeNumberField(RELEVANCE, hit.getRelevance().getScore()); - if (hit.types().size() > 0) { // TODO: Remove types rendering on Vespa 7 + if (hit.types().size() > 0) { generator.writeArrayFieldStart(TYPES); for (String t : hit.types()) { generator.writeString(t); diff --git a/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java b/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java index 0f01261f6a4..783045babf4 100644 --- a/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java +++ b/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java @@ -4,12 +4,9 @@ package com.yahoo.search.rendering; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.provider.ComponentRegistry; -import com.yahoo.prelude.templates.PageTemplateSet; -import com.yahoo.prelude.templates.SearchRendererAdaptor; -import com.yahoo.prelude.templates.TiledTemplateSet; -import com.yahoo.prelude.templates.UserTemplate; import com.yahoo.processing.rendering.Renderer; import com.yahoo.search.Result; +import com.yahoo.search.pagetemplates.result.PageTemplatesXmlRenderer; import java.util.Collection; import java.util.Collections; @@ -23,14 +20,11 @@ import java.util.concurrent.Executor; */ public final class RendererRegistry extends ComponentRegistry<com.yahoo.processing.rendering.Renderer<Result>> { - public static final ComponentId xmlRendererId = ComponentId.fromString("DefaultRenderer"); - private static final ComponentId newXmlRendererId = ComponentId.fromString("XmlRenderer"); + public static final ComponentId xmlRendererId = ComponentId.fromString("XmlRenderer"); + public static final ComponentId pageRendererId = ComponentId.fromString("PageTemplatesXmlRenderer"); public static final ComponentId jsonRendererId = ComponentId.fromString("JsonRenderer"); public static final ComponentId defaultRendererId = jsonRendererId; - private final ComponentId tiledRendererId; - private final ComponentId pageRendererId; - /** Creates a registry containing the built-in renderers only */ public RendererRegistry() { this(Collections.emptyList()); @@ -61,23 +55,19 @@ public final class RendererRegistry extends ComponentRegistry<com.yahoo.processi register(jsonRenderer.getId(), jsonRenderer); // Add xml renderer - Renderer xmlRenderer = new DefaultRenderer(executor); + Renderer xmlRenderer = new XmlRenderer(executor); xmlRenderer.initId(xmlRendererId); register(xmlRenderer.getId(), xmlRenderer); - // Add new Vespa 7 xml renderer - Renderer newXmlRenderer = new XmlRenderer(executor); - newXmlRenderer.initId(newXmlRendererId); - register(newXmlRenderer.getId(), newXmlRenderer); + // Add page templates renderer + Renderer pageRenderer = new PageTemplatesXmlRenderer(executor); + pageRenderer.initId(pageRendererId); + register(pageRenderer.getId(), pageRenderer); // add application renderers for (Renderer renderer : renderers) register(renderer.getId(), renderer); - // add legacy "templates" converted to renderers // TODO: Remove on Vespa 7 - tiledRendererId = addTemplateSet(new TiledTemplateSet()); - pageRendererId = addTemplateSet(new PageTemplateSet()); - freeze(); } @@ -86,20 +76,9 @@ public final class RendererRegistry extends ComponentRegistry<com.yahoo.processi // deconstruct the renderers which was created by this getRenderer(jsonRendererId.toSpecification()).deconstruct(); getRenderer(xmlRendererId.toSpecification()).deconstruct(); - getRenderer(newXmlRendererId.toSpecification()).deconstruct(); - getRenderer(tiledRendererId.toSpecification()).deconstruct(); getRenderer(pageRendererId.toSpecification()).deconstruct(); } - @SuppressWarnings({"deprecation", "unchecked"}) - private ComponentId addTemplateSet(UserTemplate<?> templateSet) { - Renderer renderer = new SearchRendererAdaptor(templateSet); - ComponentId rendererId = new ComponentId(templateSet.getName()); - renderer.initId(rendererId); - register(rendererId, renderer); - return rendererId; - } - /** * Returns the default JSON renderer * @@ -120,6 +99,7 @@ public final class RendererRegistry extends ComponentRegistry<com.yahoo.processi if (format == null || format.stringValue().equals("default")) return getDefaultRenderer(); if (format.stringValue().equals("json")) return getComponent(jsonRendererId); if (format.stringValue().equals("xml")) return getComponent(xmlRendererId); + if (format.stringValue().equals("page")) return getComponent(pageRendererId); com.yahoo.processing.rendering.Renderer<Result> renderer = getComponent(format); if (renderer == null) diff --git a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java index 5f99c531c95..5586fd2f996 100644 --- a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java +++ b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java @@ -349,15 +349,12 @@ public final class XmlRenderer extends AsynchronousSectionedRenderer<Result> { } private Result getResult() { - Result r; try { - r = (Result) getResponse(); + return (Result) getResponse(); } catch (ClassCastException e) { - throw new IllegalArgumentException( - "XmlRenderer attempted used outside a search context, got a " - + getResponse().getClass().getName()); + throw new IllegalArgumentException("XmlRenderer attempted used outside a search context, got a " + + getResponse().getClass().getName()); } - return r; } @Override |