org.apache.solr.response.VelocityResponseWriter Maven / Gradle / Ivy
/*
* 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.
*/
package org.apache.solr.response;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permissions;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.PropertyPermission;
import java.util.ResourceBundle;
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.SolrException;
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.generic.CollectionTool;
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.LocaleConfig;
import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.apache.velocity.tools.generic.ResourceTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CommonParams.SORT;
/**
* @deprecated since 8.4; see Deprecations
*/
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 PROPERTIES_FILE = "init.properties.file";
public static final String VELOCITY_ENABLED = "velocity.enabled";
// System property names, these are _only_ loaded at node startup (no per-request control of these)
public static final String SOLR_RESOURCE_LOADER_ENABLED = "velocity.resourceloader.solr.enabled";
// 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";
public static final String JSON_CONTENT_TYPE = "application/json;charset=UTF-8";
private File fileResourceLoaderBaseDir;
private String initPropertiesFileName; // used just to hold from init() to inform()
private boolean velocityEnabled;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private Properties velocityInitProps = new Properties();
private Map customTools = new HashMap();
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
log.warn("VelocityResponseWriter is deprecated. This may be removed in future Solr releases. Please SOLR-14065.");
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("{} specified does not exist: {}", TEMPLATE_BASE_DIR, fileResourceLoaderBaseDir);
fileResourceLoaderBaseDir = null;
} else {
if (!fileResourceLoaderBaseDir.isDirectory()) { // "*not* a directory" condition
log.warn("{} specified is not a directory: {}", TEMPLATE_BASE_DIR, fileResourceLoaderBaseDir);
fileResourceLoaderBaseDir = null;
}
}
}
initPropertiesFileName = (String) args.get(PROPERTIES_FILE);
@SuppressWarnings({"rawtypes"})
NamedList tools = (NamedList)args.get("tools");
if (tools != null) {
for(Object t : tools) {
@SuppressWarnings({"rawtypes"})
Map.Entry tool = (Map.Entry)t;
customTools.put(tool.getKey().toString(), tool.getValue().toString());
}
}
// default velocity dose not enable for all collections
Boolean vfe = args.getBooleanArg(VELOCITY_ENABLED);
velocityEnabled = (null == vfe ? false : vfe);
if (velocityEnabled) {
velocityEnabled = Boolean.getBoolean(VELOCITY_ENABLED);
}
}
@Override
public void inform(SolrCore core) {
// need to leverage SolrResourceLoader, so load init.properties.file here instead of init()
if (initPropertiesFileName != null) {
try {
velocityInitProps.load(new InputStreamReader(core.getResourceLoader().openResource(initPropertiesFileName), StandardCharsets.UTF_8));
} catch (IOException e) {
log.warn("Error loading {} specified property file: {}", PROPERTIES_FILE, initPropertiesFileName, e);
}
}
}
@Override
public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
String contentType = request.getParams().get(CONTENT_TYPE);
// Use the v.contentType specified, or either of the default content types depending on the presence of v.json
return (contentType != null) ? contentType : ((request.getParams().get(JSON) == null) ? DEFAULT_CONTENT_TYPE : JSON_CONTENT_TYPE);
}
@Override
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
if (!velocityEnabled) {
log.warn("velocity writer was not allowed, skip to write resp:" + response + " for req:" + request);
throw new IOException("velocity writer was not allowed, skip to write resp for req: " + request);
}
// run doWrite() with the velocity sandbox
try {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public Void run() throws IOException {
doWrite(writer, request, response);
return null;
}
}, VELOCITY_SANDBOX);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
// sandbox for velocity code
// TODO: we could read in a policy file instead, in case someone needs to tweak it?
private static final AccessControlContext VELOCITY_SANDBOX;
static {
Permissions permissions = new Permissions();
// TODO: restrict the scope of this! we probably only need access to classpath
permissions.add(new FilePermission("<>", "read,readlink"));
// properties needed by SolrResourceLoader (called from velocity code)
permissions.add(new PropertyPermission("jetty.testMode", "read"));
permissions.add(new PropertyPermission("solr.allow.unsafe.resourceloading", "read"));
// properties needed by log4j (called from velocity code)
permissions.add(new PropertyPermission("java.version", "read"));
// needed by velocity duck-typing
permissions.add(new RuntimePermission("accessDeclaredMembers"));
permissions.setReadOnly();
VELOCITY_SANDBOX = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, permissions) });
}
private void doWrite(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
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) {
for (int i=0; i
com.example.solr.velocity.MyTool
*/
// Custom tools can override any of the built-in tools provided above, by registering one with the same name
if (request.getCore().getCoreDescriptor().isConfigSetTrusted()) {
for (Map.Entry entry : customTools.entrySet()) {
String name = entry.getKey();
// TODO: at least log a warning when one of the *fixed* tools classes is same name with a custom one, currently silently ignored
Object customTool = SolrCore.createInstance(entry.getValue(), Object.class, "VrW custom tool: " + name, request.getCore(), request.getCore().getResourceLoader());
if (customTool instanceof LocaleConfig) {
((LocaleConfig) customTool).configure(toolConfig);
}
context.put(name, customTool);
}
}
// custom tools _cannot_ override context objects added below, like $request and $response
}
// Turn the SolrQueryResponse into a SolrResponse.
// QueryResponse has lots of conveniences suitable for a view
// Problem is, which SolrResponse class to use?
// One patch to SOLR-620 solved this by passing in a class name as
// as a parameter and using reflection and Solr's class loader to
// 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.
// 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy