SOLR-1723: VelocityResponseWriter improvements

SOLR-2035: Add a VelocityResponseWriter $resource tool for locale-specific string lookups.
Lots of VrW code cleanup, more and improved test cases.


git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1650685 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Erik Hatcher 2015-01-09 22:32:50 +00:00
parent b870aed9b9
commit ae0c13fae8
40 changed files with 689 additions and 401 deletions

View File

@ -6,8 +6,10 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/velocity" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test-files" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/resources" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -285,6 +285,9 @@ New Features
* SOLR-6766: Expose HdfsDirectoryFactory Block Cache statistics via JMX.
(Mike Drob, Mark Miller)
* SOLR-2035: Add a VelocityResponseWriter $resource tool for locale-specific string lookups.
(Erik Hatcher)
Bug Fixes
----------------------
@ -652,6 +655,8 @@ Other Changes
* SOLR-6932: All HttpClient ConnectionManagers and SolrJ clients should always be shutdown
in tests and regular code. (Mark Miller)
* SOLR-1723: VelocityResponseWriter improvements (Erik Hatcher)
================== 4.10.3 ==================
Bug Fixes

View File

@ -31,6 +31,8 @@ import java.util.Iterator;
import java.util.Map;
public class SolrParamResourceLoader extends ResourceLoader {
public static final String TEMPLATE_PARAM_PREFIX = VelocityResponseWriter.TEMPLATE + ".";
private Map<String,String> templates = new HashMap<>();
public SolrParamResourceLoader(SolrQueryRequest request) {
super();
@ -44,8 +46,8 @@ public class SolrParamResourceLoader extends ResourceLoader {
while (names.hasNext()) {
String name = names.next();
if (name.startsWith("v.template.")) {
templates.put(name.substring(11) + ".vm",params.get(name));
if (name.startsWith(TEMPLATE_PARAM_PREFIX)) {
templates.put(name.substring(TEMPLATE_PARAM_PREFIX.length()) + VelocityResponseWriter.TEMPLATE_EXTENSION,params.get(name));
}
}
}

View File

@ -0,0 +1,115 @@
package org.apache.solr.response;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.LogChute;
import org.slf4j.Logger;
public class SolrVelocityLogger implements LogChute {
private final Logger log;
public SolrVelocityLogger(Logger log) {
this.log = log;
}
@Override
public void init(RuntimeServices runtimeServices) throws Exception {
}
@Override
public void log(int level, String message) {
switch(level) {
case LogChute.TRACE_ID:
log.trace(message);
break;
case LogChute.DEBUG_ID:
log.debug(message);
break;
case LogChute.INFO_ID:
log.info(message);
break;
case LogChute.WARN_ID:
log.warn(message);
break;
case LogChute.ERROR_ID:
log.error(message);
break;
default: // unknown logging level, use warn
log.warn(message);
break;
}
}
@Override
public void log(int level, String message, Throwable throwable) {
switch(level) {
case LogChute.TRACE_ID:
log.trace(message, throwable);
break;
case LogChute.DEBUG_ID:
log.debug(message, throwable);
break;
case LogChute.INFO_ID:
log.info(message, throwable);
break;
case LogChute.WARN_ID:
log.warn(message, throwable);
break;
case LogChute.ERROR_ID:
log.error(message, throwable);
break;
default: // unknown logging level, use warn
log.warn(message, throwable);
break;
}
}
@Override
public boolean isLevelEnabled(int level) {
switch(level) {
case LogChute.TRACE_ID:
return log.isTraceEnabled();
case LogChute.DEBUG_ID:
return log.isDebugEnabled();
case LogChute.INFO_ID:
return log.isInfoEnabled();
case LogChute.WARN_ID:
return log.isWarnEnabled();
case LogChute.ERROR_ID:
return log.isErrorEnabled();
default:
return false;
}
}
}

View File

@ -17,46 +17,181 @@
package org.apache.solr.response;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import org.apache.hadoop.util.StringUtils;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SolrResponseBase;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.tools.ConversionUtils;
import org.apache.velocity.tools.generic.ComparisonDateTool;
import org.apache.velocity.tools.generic.DisplayTool;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.ListTool;
import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.apache.velocity.tools.generic.ResourceTool;
import org.apache.velocity.tools.generic.SortTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VelocityResponseWriter implements QueryResponseWriter {
public class VelocityResponseWriter implements QueryResponseWriter, SolrCoreAware {
// init param names, these are _only_ loaded at init time (no per-request control of these)
// - multiple different named writers could be created with different init params
public static final String TEMPLATE_BASE_DIR = "template.base.dir";
public static final String PARAMS_RESOURCE_LOADER_ENABLED = "params.resource.loader.enabled";
public static final String SOLR_RESOURCE_LOADER_ENABLED = "solr.resource.loader.enabled";
public static final String PROPERTIES_FILE = "init.properties.file";
// TODO: maybe pass this Logger to the template for logging from there?
// private static final Logger log = LoggerFactory.getLogger(VelocityResponseWriter.class);
// request param names
public static final String TEMPLATE = "v.template";
public static final String LAYOUT = "v.layout";
public static final String LAYOUT_ENABLED = "v.layout.enabled";
public static final String CONTENT_TYPE = "v.contentType";
public static final String JSON = "v.json";
public static final String LOCALE = "v.locale";
public static final String TEMPLATE_EXTENSION = ".vm";
public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=UTF-8";
private File fileResourceLoaderBaseDir;
private boolean paramsResourceLoaderEnabled;
private boolean solrResourceLoaderEnabled;
private String initPropertiesFileName; // used just to hold from init() to inform()
private static final Logger log = LoggerFactory.getLogger(VelocityResponseWriter.class);
private static final SolrVelocityLogger velocityLogger = new SolrVelocityLogger(log);
private Properties velocityInitProps = new Properties();
@Override
public void init(NamedList args) {
fileResourceLoaderBaseDir = null;
String templateBaseDir = (String) args.get(TEMPLATE_BASE_DIR);
if (templateBaseDir != null && !templateBaseDir.isEmpty()) {
fileResourceLoaderBaseDir = new File(templateBaseDir).getAbsoluteFile();
if (!fileResourceLoaderBaseDir.exists()) { // "*not* exists" condition!
log.warn(TEMPLATE_BASE_DIR + " specified does not exist: " + fileResourceLoaderBaseDir);
fileResourceLoaderBaseDir = null;
} else {
if (!fileResourceLoaderBaseDir.isDirectory()) { // "*not* a directory" condition
log.warn(TEMPLATE_BASE_DIR + " specified is not a directory: " + fileResourceLoaderBaseDir);
fileResourceLoaderBaseDir = null;
}
}
}
// params resource loader: off by default
Boolean prle = args.getBooleanArg(PARAMS_RESOURCE_LOADER_ENABLED);
paramsResourceLoaderEnabled = (null == prle ? false : prle);
// solr resource loader: on by default
Boolean srle = args.getBooleanArg(SOLR_RESOURCE_LOADER_ENABLED);
solrResourceLoaderEnabled = (null == srle ? true : srle);
initPropertiesFileName = (String) args.get(PROPERTIES_FILE);
}
@Override
public void inform(SolrCore core) {
// need to leverage SolrResourceLoader, so load init.properties.file here instead of init()
if (initPropertiesFileName != null) {
InputStream is = null;
try {
velocityInitProps.load(new InputStreamReader(core.getResourceLoader().openResource(initPropertiesFileName), StandardCharsets.UTF_8));
} catch (IOException e) {
log.warn("Error loading " + PROPERTIES_FILE + " specified property file: " + initPropertiesFileName, e);
}
}
}
@Override
public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
return request.getParams().get(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
}
@Override
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
VelocityEngine engine = getEngine(request); // TODO: have HTTP headers available for configuring engine
VelocityEngine engine = createEngine(request); // TODO: have HTTP headers available for configuring engine
Template template = getTemplate(engine, request);
VelocityContext context = createContext(request, response);
context.put("engine", engine); // for $engine.resourceExists(...)
String layoutTemplate = request.getParams().get(LAYOUT);
boolean layoutEnabled = request.getParams().getBool(LAYOUT_ENABLED, true) && layoutTemplate != null;
String jsonWrapper = request.getParams().get(JSON);
boolean wrapResponse = layoutEnabled || jsonWrapper != null;
// create output
if (!wrapResponse) {
// straight-forward template/context merge to output
template.merge(context, writer);
}
else {
// merge to a string buffer, then wrap with layout and finally as JSON
StringWriter stringWriter = new StringWriter();
template.merge(context, stringWriter);
if (layoutEnabled) {
context.put("content", stringWriter.toString());
stringWriter = new StringWriter();
try {
engine.getTemplate(layoutTemplate + TEMPLATE_EXTENSION).merge(context, stringWriter);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
if (jsonWrapper != null) {
writer.write(jsonWrapper + "(");
writer.write(getJSONWrap(stringWriter.toString()));
writer.write(')');
} else { // using a layout, but not JSON wrapping
writer.write(stringWriter.toString());
}
}
}
private VelocityContext createContext(SolrQueryRequest request, SolrQueryResponse response) {
VelocityContext context = new VelocityContext();
context.put("request", request);
// Register useful Velocity "tools"
context.put("esc", new EscapeTool());
context.put("date", new ComparisonDateTool());
context.put("list", new ListTool());
context.put("math", new MathTool());
context.put("number", new NumberTool());
context.put("sort", new SortTool());
context.put("display", new DisplayTool());
context.put("resource", new SolrVelocityResourceTool(
request.getCore().getSolrConfig().getResourceLoader().getClassLoader(),
request.getParams().get(LOCALE)));
// Turn the SolrQueryResponse into a SolrResponse.
// QueryResponse has lots of conveniences suitable for a view
// Problem is, which SolrResponse class to use?
@ -65,14 +200,15 @@ public class VelocityResponseWriter implements QueryResponseWriter {
// create a new instance. But for now the implementation simply
// uses QueryResponse, and if it chokes in a known way, fall back
// to bare bones SolrResponseBase.
// TODO: Can this writer know what the handler class is? With echoHandler=true it can get its string name at least
// Can this writer know what the handler class is? With echoHandler=true it can get its string name at least
SolrResponse rsp = new QueryResponse();
NamedList<Object> parsedResponse = BinaryResponseWriter.getParsedResponse(request, response);
try {
rsp.setResponse(parsedResponse);
// page only injected if QueryResponse works
context.put("page", new PageTool(request, response)); // page tool only makes sense for a SearchHandler request... *sigh*
context.put("page", new PageTool(request, response)); // page tool only makes sense for a SearchHandler request
context.put("debug",((QueryResponse)rsp).getDebugMap());
} catch (ClassCastException e) {
// known edge case where QueryResponse's extraction assumes "response" is a SolrDocumentList
// (AnalysisRequestHandler emits a "response")
@ -81,81 +217,69 @@ public class VelocityResponseWriter implements QueryResponseWriter {
}
context.put("response", rsp);
// Velocity context tools - TODO: make these pluggable
context.put("esc", new EscapeTool());
context.put("date", new ComparisonDateTool());
context.put("list", new ListTool());
context.put("math", new MathTool());
context.put("number", new NumberTool());
context.put("sort", new SortTool());
context.put("display", new DisplayTool());
context.put("engine", engine); // for $engine.resourceExists(...)
String layout_template = request.getParams().get("v.layout");
String json_wrapper = request.getParams().get("v.json");
boolean wrap_response = (layout_template != null) || (json_wrapper != null);
// create output, optionally wrap it into a json object
if (wrap_response) {
StringWriter stringWriter = new StringWriter();
template.merge(context, stringWriter);
if (layout_template != null) {
context.put("content", stringWriter.toString());
stringWriter = new StringWriter();
try {
engine.getTemplate(layout_template + ".vm").merge(context, stringWriter);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
if (json_wrapper != null) {
writer.write(request.getParams().get("v.json") + "(");
writer.write(getJSONWrap(stringWriter.toString()));
writer.write(')');
} else { // using a layout, but not JSON wrapping
writer.write(stringWriter.toString());
}
} else {
template.merge(context, writer);
}
return context;
}
private VelocityEngine getEngine(SolrQueryRequest request) {
private VelocityEngine createEngine(SolrQueryRequest request) {
VelocityEngine engine = new VelocityEngine();
engine.setProperty("params.resource.loader.instance", new SolrParamResourceLoader(request));
SolrVelocityResourceLoader resourceLoader =
new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader());
engine.setProperty("solr.resource.loader.instance", resourceLoader);
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "params,solr");
// route all Velocity logging through Solr's logging facility
engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogger);
// TODO: Externalize Velocity properties
String propFile = request.getParams().get("v.properties");
try {
Properties props = new Properties();
// Don't create a separate velocity log file by default.
props.put(RuntimeConstants.RUNTIME_LOG, "");
// Set some engine properties that improve the experience
// - these could be considered in the future for parameterization, but can also be overridden by using
// the init.properties.file setting. (TODO: add a test for this)
if (propFile == null) {
engine.init(props);
} else {
InputStream is = null;
try {
is = resourceLoader.getResourceStream(propFile);
props.load(new InputStreamReader(is, StandardCharsets.UTF_8));
engine.init(props);
}
finally {
if (is != null) is.close();
}
}
// load the built-in _macros.vm first, then load VM_global_library.vm for legacy (pre-5.0) support,
// and finally allow macros.vm to have the final say and override anything defined in the preceding files.
engine.setProperty(RuntimeConstants.VM_LIBRARY, "_macros.vm,VM_global_library.vm,macros.vm");
// Standard templates autoload, but not the macro one(s), by default, so let's just make life
// easier, and consistent, for macro development too.
engine.setProperty(RuntimeConstants.VM_LIBRARY_AUTORELOAD, "true");
/*
Set up Velocity resource loader(s)
terminology note: "resource loader" is overloaded here, there is Solr's resource loader facility for plugins,
and there are Velocity template resource loaders. It's confusing, they overlap: there is a Velocity resource
loader that loads templates from Solr's resource loader (SolrVelocityResourceLoader).
The Velocity resource loader order is [params,][file,][solr], intentionally ordered in this manner, and each
one optional and individually enable-able. By default, only "solr" (resource loader) is used, parsing templates
from a velocity/ sub-tree in either the classpath or under conf/.
A common usage would be to enable the file template loader, keeping the solr loader enabled; the Velocity resource
loader path would then be "file,solr" (params is disabled by default). The basic browse templates are built into
this plugin, but can be individually overridden by placing a same-named template in the template.base.dir specified
directory.
*/
ArrayList<String> loaders = new ArrayList<String>();
if (paramsResourceLoaderEnabled) {
loaders.add("params");
engine.setProperty("params.resource.loader.instance", new SolrParamResourceLoader(request));
}
catch (Exception e) {
throw new RuntimeException(e);
if (fileResourceLoaderBaseDir != null) {
loaders.add("file");
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, fileResourceLoaderBaseDir.getAbsolutePath());
}
if (solrResourceLoaderEnabled) {
// The solr resource loader serves templates under a velocity/ subtree from <lib>, conf/,
// or SolrCloud's configuration tree. Or rather the other way around, other resource loaders are rooted
// from the top, whereas this is velocity/ sub-tree rooted.
loaders.add("solr");
engine.setProperty("solr.resource.loader.instance", new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader()));
}
// Always have the built-in classpath loader. This is needed when using VM_LIBRARY macros, as they are required
// to be present if specified, and we want to have a nice macros facility built-in for users to use easily, and to
// extend in custom ways.
loaders.add("builtin");
engine.setProperty("builtin.resource.loader.instance", new ClasspathResourceLoader());
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, StringUtils.join(",", loaders));
// bring in any custom properties too
engine.init(velocityInitProps);
return engine;
}
@ -163,18 +287,19 @@ public class VelocityResponseWriter implements QueryResponseWriter {
private Template getTemplate(VelocityEngine engine, SolrQueryRequest request) throws IOException {
Template template;
String template_name = request.getParams().get("v.template");
String qt = request.getParams().get("qt");
String templateName = request.getParams().get(TEMPLATE);
String qt = request.getParams().get(CommonParams.QT);
String path = (String) request.getContext().get("path");
if (template_name == null && path != null) {
template_name = path;
if (templateName == null && path != null) {
templateName = path;
} // TODO: path is never null, so qt won't get picked up maybe special case for '/select' to use qt, otherwise use path?
if (template_name == null && qt != null) {
template_name = qt;
if (templateName == null && qt != null) {
templateName = qt;
}
if (template_name == null) template_name = "index";
if (templateName == null) templateName = "index";
try {
template = engine.getTemplate(template_name + ".vm");
template = engine.getTemplate(templateName + TEMPLATE_EXTENSION);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
@ -182,12 +307,7 @@ public class VelocityResponseWriter implements QueryResponseWriter {
return template;
}
@Override
public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
return request.getParams().get("v.contentType", "text/html;charset=UTF-8");
}
private String getJSONWrap(String xmlResult) { // TODO: maybe noggit or Solr's JSON utilities can make this cleaner?
private String getJSONWrap(String xmlResult) { // maybe noggit or Solr's JSON utilities can make this cleaner?
// escape the double quotes and backslashes
String replace1 = xmlResult.replaceAll("\\\\", "\\\\\\\\");
replace1 = replace1.replaceAll("\\n", "\\\\n");
@ -197,7 +317,34 @@ public class VelocityResponseWriter implements QueryResponseWriter {
return "{\"result\":\"" + replaced + "\"}";
}
@Override
public void init(NamedList args) {
// see: http://svn.apache.org/repos/asf/velocity/tools/branches/2.0.x/src/main/java/org/apache/velocity/tools/generic/ResourceTool.java
private class SolrVelocityResourceTool extends ResourceTool {
private final Locale locale;
private ClassLoader solrClassLoader;
public SolrVelocityResourceTool(ClassLoader cl, String localeString) {
this.solrClassLoader = cl;
Locale l = toLocale(localeString);
this.locale = (l == null ? Locale.ROOT : l);
}
@Override
protected ResourceBundle getBundle(String baseName, Object loc) {
// resource bundles for this tool must be in velocity "package"
return ResourceBundle.getBundle("velocity." + baseName, locale, solrClassLoader);
}
// Why did Velocity Tools make this private? Copied from ResourceTools.java
private Locale toLocale(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof Locale) {
return (Locale) obj;
}
String s = String.valueOf(obj);
return ConversionUtils.toLocale(s);
}
}
}

View File

@ -0,0 +1,4 @@
## legacy support, the writer will load this as a macro library as it used to
## but best not to have your own file called VM_global_library.vm; put them in macros.vm instead.
## This file is needed for the "builtin" resource loader as Velocity requires all macro library files exist, but
## we don't want users to have to have a macro library file in their template directories.

View File

@ -0,0 +1,32 @@
#macro(param $key)$request.params.get($key)#end
#macro(url_root)/solr#end
#macro(core_name)$request.core.name#end
#macro(url_for_solr)#{url_root}#if($request.core.name != "")/$request.core.name#end#end
#macro(url_for_home)#url_for_solr$request.context.path#end
#macro(q)&q=$!{esc.url($request.params.get('q'))}#end
#macro(fqs $p)#foreach($fq in $p)#if($velocityCount>1)&#{end}fq=$esc.url($fq)#end#end
#macro(debug)#if($debug)&debug=true#end#end
#macro(sort $p)#if($p)#foreach($s in $p)&sort=$esc.url($s)#end#end#end
#macro(lensNoQ)?#if($request.params.getParams('fq') and $list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#sort($request.params.getParams('sort'))#debug#end
#macro(lens)#lensNoQ#q#end
#macro(url_for_lens)#{url_for_home}#lens#end
#macro(url_for_start $start)#url_for_home#lens&start=$start#end
#macro(url_for_filters $p)#url_for_home?#q#if($list.size($p) > 0)&#fqs($p)#end#debug#end
#macro(url_for_nested_facet_query $field)#url_for_home#lens&fq=$esc.url($field)#end
#macro(url_for_facet_filter $field $value)#url_for_home#lens&fq=#if($value!=$null)$esc.url($field):%22$esc.url($value)%22#else-$esc.url($field):[*+TO+*]#end#end
#macro(url_for_facet_date_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end
#macro(url_for_facet_range_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end

View File

@ -0,0 +1,3 @@
## placeholder for users to specify their own macros
## This file is needed for the "builtin" resource loader as Velocity requires all macro library files exist.
## This is the file we want users to override to add their own macros.

View File

@ -1,82 +0,0 @@
#**
* Global macros used by other templates.
* This file must be named VM_global_library.vm
* in order for Velocity to find it.
*#
#macro(param $key)$request.params.get($key)#end
#macro(url_root)/solr#end
## TODO: s/url_for_solr/url_for_core/ and s/url_root/url_for_solr/
#macro(core_name)$request.core.name#end
#macro(url_for_solr)#{url_root}#if($request.core.name != "")/$request.core.name#end#end
#macro(url_for_home)#url_for_solr/browse#end ## TODO: What request handler path to use for _home?
#macro(q)&q=$!{esc.url($params.get('q'))}#end
#macro(fqs $p)#foreach($fq in $p)#if($velocityCount>1)&#{end}fq=$esc.url($fq)#end#end
#macro(debug)#if($request.params.get('debugQuery'))&debugQuery=true#end#end ## TODO: leverage actual debug mode setting, or at least also support debug=all/query/etc
#macro(sort $p)#if($p)#foreach($s in $p)&sort=$esc.url($s)#end#end#end
#macro(lensNoQ)?#if($request.params.getParams('fq') and $list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#sort($request.params.getParams('sort'))#debug#end
#macro(lens)#lensNoQ#q#end
#macro(url_for_lens)#{url_for_home}#lens#end
#macro(url_for_start $start)#url_for_home#lens&start=$start#end
#macro(url_for_filters $p)#url_for_home?#q#if($list.size($p) > 0)&#fqs($p)#end#debug#end
#macro(url_for_nested_facet_query $field)#url_for_home#lens&fq=$esc.url($field)#end
## TODO: convert to use {!raw f=$field}$value (with escaping of course)
#macro(url_for_facet_filter $field $value)#url_for_home#lens&fq=#if($value!=$null)$esc.url($field):%22$esc.url($value)%22#else-$esc.url($field):[*+TO+*]#end#end
#macro(url_for_facet_date_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end
#macro(url_for_facet_range_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end
#macro(link_to_previous_page $text)
#if($page.current_page_number > 1)
#set($prev_start = $page.start - $page.results_per_page)
<a class="prev-page" href="#url_for_start($prev_start)">$text</a>
#end
#end
#macro(link_to_next_page $text)
#if($page.current_page_number < $page.page_count)
#set($next_start = $page.start + $page.results_per_page)
<a class="next-page" href="#url_for_start($next_start)">$text</a>
#end
#end
#macro(link_to_page $page_number $text)
#if($page_number == $page.current_page_number)
$text
#else
#if($page_number <= $page.page_count)
#set($page_start = $page_number * $page.results_per_page - $page.results_per_page)
<a class="page" href="#url_for_start($page_start)">$text</a>
#end
#end
#end
#macro(field $f)
#if($response.response.highlighting.get($docId).get($f).get(0))
#set($pad = "")
#foreach($v in $response.response.highlighting.get($docId).get($f))
$pad$v##
#set($pad = " ... ")
#end
#else
#foreach($v in $doc.getFieldValues($f))
$v##
#end
#end
#end

View File

@ -1,5 +1,3 @@
#set($params = $request.params)
## Show Error Message, if any
<div class="error">
#parse("error.vm")
@ -7,11 +5,11 @@
<div class="query-box">
<form id="query-form" action="#{url_for_home}" method="GET">
Find:
<input type="text" id="q" name="q" value="$!esc.html($params.get('q'))"/>
$resource.find:
<input type="text" id="q" name="q" value="$!esc.html($request.params.get('q'))"/>
<input type="submit"/>
#if($request.params.get('debugQuery'))
#if($debug) ## TODO: this would automatically happen when arbitrary parameters are kept on URLs
<input type="hidden" name="debug" value="true"/>
#end
#foreach($fq in $request.params.getParams('fq'))
@ -19,7 +17,7 @@
#end
<div class="constraints">
#foreach($fq in $params.getParams('fq'))
#foreach($fq in $request.params.getParams('fq'))
#set($previous_fq_count=$velocityCount - 1)
#if($fq != '')
&gt;
@ -29,7 +27,7 @@
</div>
<div class="parsed_query_header">
#if($request.params.get('debugQuery'))
#if($debug)
<a href="#" onclick='jQuery(this).siblings("div").toggle(); return false;'>toggle parsed query</a>
<div class="parsed_query" style="display:none">$response.response.debug.parsedquery</div>
#end
@ -46,11 +44,10 @@
<span>
<span class="results-found">$page.results_found</span>
results found in
${response.responseHeader.QTime} ms
${response.responseHeader.QTime}ms
</span>
Page <span class="page-num">$page.current_page_number</span>
of <span class="page-count">$page.page_count</span>
$resource.page_of.insert($page.current_page_number,$page.page_count)
</div>
## Render Results, actual matching docs
@ -59,13 +56,12 @@
</div>
<div class="pagination">
#link_to_previous_page("previous")
#link_to_previous_page
<span class="results-found">$page.results_found</span>
results found.
Page <span class="page-num">$page.current_page_number</span>
of <span class="page-count">$page.page_count</span>
$resource.page_of.insert($page.current_page_number,$page.page_count)
#link_to_next_page("next")
#link_to_next_page
</div>

View File

@ -1,26 +0,0 @@
#**
* Show Debugging Information, if enabled
*#
#if( $params.getBool("debugQuery",false) )
<a href="#" onclick='jQuery(this).siblings("pre").toggle(); return false;'>toggle explain</a>
<pre style="display:none">
$response.getExplainMap().get($doc.getFirstValue('id'))
</pre>
<a href="#" onclick='jQuery(this).siblings("pre2").toggle(); return false;'>toggle all fields</a>
<pre2 style="display:none">
#foreach($fieldname in $doc.fieldNames)
<br>
<span class="field-name">$fieldname :</span>
<span>
#foreach($value in $doc.getFieldValues($fieldname))
$esc.html($value)
#end
</span>
</br>
#end
</pre2>
#end

View File

@ -1,10 +1,3 @@
#**
* Show Error Message, if any
*#
## Show Error Message, if any
## Usually rendered inside div class=error
#if( $response.response.error.code )
<h1>ERROR $response.response.error.code</h1>
$response.response.error.msg

View File

@ -2,14 +2,14 @@
<div>
<span>Options:</span>
#if($request.params.get('debugQuery'))
#if($debug)
<a href="#url_for_home?#q#if($list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end">
disable debug</a>
#else
<a href="#url_for_lens&debug=true&fl=*,score">enable debug</a>
#end
-
<a href="#url_for_lens&wt=xml#if($request.params.get('debugQuery'))&debug=true#end">XML results</a>
<a href="#url_for_lens&wt=xml#if($debug)&debug=true#end">XML results</a> ## TODO: Add links for other formats, maybe dynamically?
</div>

View File

@ -3,9 +3,12 @@
*#
<title>Solr browse: #core_name</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<link rel="icon" type="image/x-icon" href="#{url_root}/img/favicon.ico"/>
<link rel="shortcut icon" type="image/x-icon" href="#{url_root}/img/favicon.ico"/>
<script type="text/javascript" src="#{url_root}/js/lib/jquery-1.7.2.min.js"></script>
<style>
@ -17,17 +20,6 @@
#head{
width: 100%;
}
.array-field {
border: 2px solid #474747;
background: #FFE9D8;
padding: 5px;
margin: 5px;
}
.array-field-list li {
list-style: circle;
margin-left: 20px;
}
.parsed_query_header {
font-family: Helvetica, Arial, sans-serif;
@ -47,7 +39,11 @@
}
a {
color: #43a4b1;
color: #305CB3;
}
em {
color: #FF833D;
}
.facets {
@ -61,7 +57,7 @@
}
.facets h2 {
background: #EA897E;
background: #D9411E;
padding: 2px 5px;
}
@ -82,18 +78,9 @@
font-weight: bold;
}
.highlight {
color: white;
background-color: gray;
border: 1px black solid;
}
.highlight-box {
margin-left: 15px;
}
.field-name {
font-weight: bold;
// align="right" valign="top"
}
.highlighted-facet-field {

View File

@ -1,24 +1,27 @@
#set($docId = $doc.getFieldValue('id'))
#set($docId = $doc.getFirstValue($request.schema.uniqueKeyField.name))
<div class="result-document">
<table>
#foreach( $fieldName in $doc.fieldNames )
#foreach( $value in $doc.getFieldValues($fieldName) )
<tr>
<th align="right" valign="top">
#if( $foreach.count == 1 )
$fieldName:
#end
<th align="right" valign="top" style="field-name">
$esc.html($fieldName):
</th>
<td align="left" valign="top">
$esc.html($value) <br/>
#field($fieldName)
</td>
</tr>
#end
#end
</table>
#if($debug)
<a href="#" onclick='jQuery(this).siblings("pre").toggle(); return false;'>toggle explain</a>
<pre style="display:none">
$response.getExplainMap().get($docId)
</pre>
#end
</div>

View File

@ -5,7 +5,7 @@
<body>
<div id="admin"><a href="#url_root/#/#core_name">Solr Admin</a></div>
<div id="head">
<a href="#url_for_home#if($request.params.get('debugQuery'))?debugQuery=true#end"><img src="#{url_root}/img/solr.svg" id="logo"/></a>
<a href="#url_for_home#if($debug)?debug=true#end"><img src="#{url_root}/img/solr.svg" id="logo"/></a>
</div>
<div id="content">

View File

@ -0,0 +1,38 @@
# TODO: make this parameterized fully, no context sensitivity
#macro(field $f)
#if($response.response.highlighting.get($docId).get($f).get(0))
#set($pad = "")
#foreach($v in $response.response.highlighting.get($docId).get($f))
$pad$v## #TODO: $esc.html() or maybe make that optional?
#set($pad = " ... ")
#end
#else
#foreach($v in $doc.getFieldValues($f))$esc.html($v)
#end
#end
#end
#macro(link_to_previous_page)
#if($page.current_page_number > 1)
#set($prev_start = $page.start - $page.results_per_page)
<a class="prev-page" href="#url_for_start($prev_start)">$resource.previous</a>
#end
#end
#macro(link_to_next_page)
#if($page.current_page_number < $page.page_count)
#set($next_start = $page.start + $page.results_per_page)
<a class="next-page" href="#url_for_start($next_start)">$resource.next</a>
#end
#end
#macro(link_to_page $page_number $text)
#if($page_number == $page.current_page_number)
$text
#else
#if($page_number <= $page.page_count)
#set($page_start = $page_number * $page.results_per_page - $page.results_per_page)
<a class="page" href="#url_for_start($page_start)">$text</a>
#end
#end
#end

View File

@ -0,0 +1,6 @@
find=Find
page_of=Page <span class="page-num">{0}</span> of <span class="page-count">{1}</span>
previous=previous
next=next

View File

@ -0,0 +1 @@
testing

View File

@ -16,66 +16,13 @@
limitations under the License.
-->
<!--
For more details about configurations options that may appear in
this file, see http://wiki.apache.org/solr/SolrConfigXml.
-->
<config>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<indexConfig>
<useCompoundFile>${useCompoundFile:false}</useCompoundFile>
</indexConfig>
<lib dir="../../contrib/velocity/lib" />
<lib dir="../../dist/" regex="solr-velocity-\d.*\.jar" />
<dataDir>${solr.data.dir:}</dataDir>
<!--<lib dir="../../contrib/velocity/lib" />-->
<!--<lib dir="../../dist/" regex="solr-velocity-\d.*\.jar" />-->
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.StandardDirectoryFactory}"/>
<updateHandler class="solr.DirectUpdateHandler2">
</updateHandler>
<query>
<maxBooleanClauses>1024</maxBooleanClauses>
<filterCache class="solr.FastLRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<queryResultCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<documentCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<enableLazyFieldLoading>true</enableLazyFieldLoading>
<queryResultWindowSize>20</queryResultWindowSize>
<queryResultMaxDocsCached>200</queryResultMaxDocsCached>
<listener event="newSearcher" class="solr.QuerySenderListener">
<arr name="queries">
</arr>
</listener>
<listener event="firstSearcher" class="solr.QuerySenderListener">
<arr name="queries">
<lst>
<str name="q">static firstSearcher warming in solrconfig.xml</str>
</lst>
</arr>
</listener>
<useColdSearcher>false</useColdSearcher>
<maxWarmingSearchers>2</maxWarmingSearchers>
</query>
<requestDispatcher handleSelect="true" >
<requestParsers enableRemoteStreaming="true"
multipartUploadLimitInKB="2048000" />
<httpCaching never304="true" />
</requestDispatcher>
<requestHandler name="search" class="solr.SearchHandler" default="true">
<lst name="defaults">
<str name="echoParams">explicit</str>
@ -83,13 +30,9 @@
</lst>
</requestHandler>
<requestHandler name="/update" class="solr.UpdateRequestHandler" />
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter"/>
<!-- Legacy config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
</admin>
<queryResponseWriter name="velocityWithInitProps" class="solr.VelocityResponseWriter">
<str name="init.properties.file">velocity-init.properties</str>
</queryResponseWriter>
</config>

View File

@ -0,0 +1,18 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
foreach.provide.scope.control=false

View File

@ -1,50 +1,3 @@
#macro(legacy_macro)legacy_macro_SUCCESS#end
#macro(nl2ul $named_list)
<ul>
#foreach($kv in $named_list)
<li>$kv.key ($kv.value)
#nl2ul($kv.value)
</li>
#end
</ul>
#end
#macro(url_for_filters $filter_queries)
/solr/itas?fq=$filter_queries
#end
#macro(url_for_home)/solr/itas#end
#macro(url_for_start $start)/solr/itas?start=$start&q=$!{esc.url($params.get('q'))}#end
## TODO: need to add escaping
#macro(url_for_nested_facet $field $value)/solr/itas?fq=$field:%22$value%22#end
#macro(link_to_previous_page $text)
#if($page.current_page_number > 1)
#set($prev_start = $page.start - $page.results_per_page)
<a class="prev-page" href="#url_for_start($prev_start)">$text</a>
#end
#end
#macro(link_to_next_page $text)
#if($page.current_page_number < $page.page_count)
#set($next_start = $page.start + $page.results_per_page)
<a class="next-page" href="#url_for_start($next_start)">$text</a>
#end
#end
#macro(link_to_page $page_number $text)
#if($page_number == $page.current_page_number)
$text
#else
#if($page_number <= $page.page_count)
#set($page_start = $page_number * $page.results_per_page - $page.results_per_page)
<a class="page" href="#url_for_start($page_start)">$text</a>
#end
#end
#end
#macro(param $key)$request.params.get($key)#end
#macro(url_root)Loaded from: VM_global_library.vm#end

View File

@ -0,0 +1 @@
#foreach($x in ["a","b"])$!foreach.index#end

View File

@ -0,0 +1,3 @@
#macro(test_macro)test_macro_SUCCESS#end
#macro(url_root)Loaded from: macros.vm#end

View File

@ -18,27 +18,39 @@
package org.apache.solr.velocity;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.response.SolrParamResourceLoader;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.response.VelocityResponseWriter;
import org.apache.solr.request.SolrQueryRequest;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
public class VelocityResponseWriterTest extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig.xml", "schema.xml", getFile("velocity/solr").getAbsolutePath());
System.out.println(getFile("velocity/solr").getAbsolutePath());
}
@Test
public void testVelocityResponseWriterRegistered() {
QueryResponseWriter writer = h.getCore().getQueryResponseWriter("velocity");
assertTrue("VrW registered check", writer instanceof VelocityResponseWriter);
}
@Test
public void testCustomParamTemplate() throws Exception {
// This test doesn't use the Solr core, just the response writer directly
org.apache.solr.response.VelocityResponseWriter vrw = new VelocityResponseWriter();
SolrQueryRequest req = req("v.template","custom", "v.template.custom","$response.response.response_data");
NamedList<String> nl = new NamedList<String>();
nl.add(VelocityResponseWriter.PARAMS_RESOURCE_LOADER_ENABLED, "true");
vrw.init(nl);
SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"custom",
SolrParamResourceLoader.TEMPLATE_PARAM_PREFIX+"custom","$response.response.response_data");
SolrQueryResponse rsp = new SolrQueryResponse();
StringWriter buf = new StringWriter();
rsp.add("response_data", "testing");
@ -47,14 +59,92 @@ public class VelocityResponseWriterTest extends SolrTestCaseJ4 {
}
@Test
public void testVelocityResponseWriterRegistered() {
QueryResponseWriter writer = h.getCore().getQueryResponseWriter("velocity");
public void testParamResourceLoaderDisabled() throws Exception {
org.apache.solr.response.VelocityResponseWriter vrw = new VelocityResponseWriter();
// by default param resource loader is disabled, no need to set it here
SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"custom",
SolrParamResourceLoader.TEMPLATE_PARAM_PREFIX+"custom","$response.response.response_data");
SolrQueryResponse rsp = new SolrQueryResponse();
StringWriter buf = new StringWriter();
try {
vrw.write(buf, req, rsp);
fail("Should have thrown exception due to missing template");
} catch (IOException e) {
// expected exception
}
}
assertTrue("VrW registered check", writer instanceof VelocityResponseWriter);
@Test
public void testFileResourceLoader() throws Exception {
org.apache.solr.response.VelocityResponseWriter vrw = new VelocityResponseWriter();
NamedList<String> nl = new NamedList<String>();
nl.add("template.base.dir", getFile("velocity").getAbsolutePath());
vrw.init(nl);
SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"file");
SolrQueryResponse rsp = new SolrQueryResponse();
StringWriter buf = new StringWriter();
vrw.write(buf, req, rsp);
assertEquals("testing", buf.toString());
}
@Test
public void testSolrResourceLoaderTemplate() throws Exception {
assertEquals("0", h.query(req("q","*:*", "wt","velocity","v.template","numFound")));
assertEquals("0", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"numFound")));
}
@Test
public void testMacros() throws Exception {
// tests that a macro in a custom macros.vm is visible
assertEquals("test_macro_SUCCESS", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_visible")));
// tests that a builtin (_macros.vm) macro, #url_root in this case, can be overridden in a custom macros.vm
// the macro is also defined in VM_global_library.vm, which should also be overridden by macros.vm
assertEquals("Loaded from: macros.vm", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_overridden")));
// tests that macros defined in VM_global_library.vm are visible. This file was where macros in pre-5.0 versions were defined
assertEquals("legacy_macro_SUCCESS", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_legacy_support")));
}
@Test
public void testInitProps() throws Exception {
// The test init properties file turns off being able to use $foreach.index (the implicit loop counter)
// The foreach.vm template uses $!foreach.index, with ! suppressing the literal "$foreach.index" output
assertEquals("01", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"foreach")));
assertEquals("", h.query(req("q","*:*", "wt","velocityWithInitProps",VelocityResponseWriter.TEMPLATE,"foreach")));
}
@Test
public void testLocaleFeature() throws Exception {
assertEquals("Color", h.query(req("q", "*:*", "wt", "velocity", VelocityResponseWriter.TEMPLATE, "locale",
VelocityResponseWriter.LOCALE,"en_US")));
assertEquals("Colour", h.query(req("q", "*:*", "wt", "velocity", VelocityResponseWriter.TEMPLATE, "locale",
VelocityResponseWriter.LOCALE,"en_UK")));
}
@Test
public void testLayoutFeature() throws Exception {
assertEquals("{{{0}}}", h.query(req("q","*:*", "wt","velocity",
VelocityResponseWriter.TEMPLATE,"numFound", VelocityResponseWriter.LAYOUT,"layout")));
// even with v.layout specified, layout can be disabled explicitly
assertEquals("0", h.query(req("q","*:*", "wt","velocity",
VelocityResponseWriter.TEMPLATE,"numFound",
VelocityResponseWriter.LAYOUT,"layout",
VelocityResponseWriter.LAYOUT_ENABLED,"false")));
}
@Test
public void testJSONWrapper() throws Exception {
assertEquals("foo({\"result\":\"0\"})", h.query(req("q", "*:*", "wt", "velocity",
VelocityResponseWriter.TEMPLATE, "numFound",
VelocityResponseWriter.JSON,"foo")));
// Now with layout, for good measure
assertEquals("foo({\"result\":\"{{{0}}}\"})", h.query(req("q", "*:*", "wt", "velocity",
VelocityResponseWriter.TEMPLATE, "numFound",
VelocityResponseWriter.JSON,"foo",
VelocityResponseWriter.LAYOUT,"layout")));
}
}

View File

@ -0,0 +1,18 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
color=Color

View File

@ -0,0 +1,18 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
color=Colour

View File

@ -1727,8 +1727,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -1698,8 +1698,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -1695,8 +1695,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -1726,8 +1726,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -1704,8 +1704,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -0,0 +1,20 @@
{"params":{
"query":{
"defType":"edismax",
"q.alt":"*:*",
"rows":"10",
"fl":"*,score",
"":{"v":0}
},
"facets":{
"facet":"on",
"facet.mincount": "1",
"":{"v":0}
},
"velocity":{
"wt": "velocity",
"v.template":"browse",
"v.layout": "layout",
"":{"v":0}
}
}}

View File

@ -860,24 +860,9 @@
(SearchHandler) can be registered multiple times with different
names (and different init parameters)
-->
<requestHandler name="/browse" class="solr.SearchHandler">
<requestHandler name="/browse" class="solr.SearchHandler" useParams="query,facets,velocity,browse">
<lst name="defaults">
<str name="echoParams">explicit</str>
<!-- VelocityResponseWriter settings -->
<str name="wt">velocity</str>
<str name="v.template">browse</str>
<str name="v.layout">layout</str>
<!-- Query settings -->
<str name="defType">edismax</str>
<str name="q.alt">*:*</str>
<str name="rows">10</str>
<str name="fl">*,score</str>
<!-- Faceting defaults -->
<str name="facet">on</str>
<str name="facet.mincount">1</str>
</lst>
</requestHandler>
@ -1541,8 +1526,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for

View File

@ -1762,7 +1762,9 @@
<!--
Custom response writers can be declared as needed...
-->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
<str name="template.base.dir">${velocity.template.base.dir:}</str>
</queryResponseWriter>
<!-- XSLT response writer transforms the XML output by any xslt file found