/** * 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 org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.SolrResponseBase; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.tools.generic.*; import java.io.*; import java.util.Properties; public class VelocityResponseWriter implements QueryResponseWriter { // TODO: maybe pass this Logger to the template for logging from there? // private static final Logger log = LoggerFactory.getLogger(VelocityResponseWriter.class); public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException { VelocityEngine engine = getEngine(request); // TODO: have HTTP headers available for configuring engine Template template = getTemplate(engine, request); VelocityContext context = new VelocityContext(); context.put("request", request); // 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. // TODO: 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 = new EmbeddedSolrServer(request.getCore()).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* } catch (ClassCastException e) { // known edge case where QueryResponse's extraction assumes "response" is a SolrDocumentList // (AnalysisRequestHandler emits a "response") e.printStackTrace(); rsp = new SolrResponseBase(); rsp.setResponse(parsedResponse); } 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("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); } } private VelocityEngine getEngine(SolrQueryRequest request) { VelocityEngine engine = new VelocityEngine(); String template_root = request.getParams().get("v.base_dir"); File baseDir = new File(request.getCore().getResourceLoader().getConfigDir(), "velocity"); if (template_root != null) { baseDir = new File(template_root); } engine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, baseDir.getAbsolutePath()); engine.setProperty("params.resource.loader.instance", new SolrParamResourceLoader(request)); SolrVelocityResourceLoader resourceLoader = new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader()); engine.setProperty("solr.resource.loader.instance", resourceLoader); // TODO: Externalize Velocity properties engine.setProperty(VelocityEngine.RESOURCE_LOADER, "params,file,solr"); String propFile = request.getParams().get("v.properties"); try { if (propFile == null) engine.init(); else { InputStream is = null; try { is = resourceLoader.getResourceStream(propFile); Properties props = new Properties(); props.load(is); engine.init(props); } finally { if (is != null) is.close(); } } } catch (Exception e) { throw new RuntimeException(e); } return engine; } 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 path = (String) request.getContext().get("path"); if (template_name == null && path != null) { template_name = 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 (template_name == null) template_name = "index"; try { template = engine.getTemplate(template_name + ".vm"); } catch (Exception e) { throw new IOException(e.getMessage()); } return template; } 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? // escape the double quotes and backslashes String replace1 = xmlResult.replaceAll("\\\\", "\\\\\\\\"); replace1 = replace1.replaceAll("\\n", "\\\\n"); replace1 = replace1.replaceAll("\\r", "\\\\r"); String replaced = replace1.replaceAll("\"", "\\\\\""); // wrap it in a JSON object return "{\"result\":\"" + replaced + "\"}"; } public void init(NamedList args) { } }