/* * 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.util; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.XML; import org.w3c.dom.Document; import org.xml.sax.SAXException; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; abstract public class BaseTestHarness { private static final ThreadLocal<DocumentBuilder> builderTL = new ThreadLocal<>(); private static final ThreadLocal<XPath> xpathTL = new ThreadLocal<>(); public static DocumentBuilder getXmlDocumentBuilder() { try { DocumentBuilder builder = builderTL.get(); if (builder == null) { builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); builderTL.set(builder); } return builder; } catch (Exception e) { throw new RuntimeException(e); } } public static XPath getXpath() { try { XPath xpath = xpathTL.get(); if (xpath == null) { xpath = XPathFactory.newInstance().newXPath(); xpathTL.set(xpath); } return xpath; } catch (Exception e) { throw new RuntimeException(e); } } /** * A helper method which validates a String against an array of XPath test * strings. * * @param xml The xml String to validate * @param tests Array of XPath strings to test (in boolean mode) on the xml * @return null if all good, otherwise the first test that fails. */ public static String validateXPath(String xml, String... tests) throws XPathExpressionException, SAXException { if (tests==null || tests.length == 0) return null; Document document = null; try { document = getXmlDocumentBuilder().parse(new ByteArrayInputStream (xml.getBytes(StandardCharsets.UTF_8))); } catch (UnsupportedEncodingException e1) { throw new RuntimeException("Totally weird UTF-8 exception", e1); } catch (IOException e2) { throw new RuntimeException("Totally weird io exception", e2); } for (String xp : tests) { xp=xp.trim(); Boolean bool = (Boolean) getXpath().evaluate(xp, document, XPathConstants.BOOLEAN); if (!bool) { return xp; } } return null; } public static Object evaluateXPath(String xml, String xpath, QName returnType) throws XPathExpressionException, SAXException { if (null == xpath) return null; Document document = null; try { document = getXmlDocumentBuilder().parse(new ByteArrayInputStream (xml.getBytes(StandardCharsets.UTF_8))); } catch (UnsupportedEncodingException e1) { throw new RuntimeException("Totally weird UTF-8 exception", e1); } catch (IOException e2) { throw new RuntimeException("Totally weird io exception", e2); } xpath = xpath.trim(); return getXpath().evaluate(xpath.trim(), document, returnType); } /** * A helper that creates an xml <doc> containing all of the * fields and values specified * * @param fieldsAndValues 0 and Even numbered args are fields names odds are field values. */ public static String makeSimpleDoc(String... fieldsAndValues) { try { StringWriter w = new StringWriter(); w.append("<doc>"); for (int i = 0; i < fieldsAndValues.length; i+=2) { XML.writeXML(w, "field", fieldsAndValues[i + 1], "name", fieldsAndValues[i]); } w.append("</doc>"); return w.toString(); } catch (IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Generates a delete by query xml string * @param q Query that has not already been xml escaped * @param args The attributes of the delete tag */ public static String deleteByQuery(String q, String... args) { try { StringWriter r = new StringWriter(); XML.writeXML(r, "query", q); return delete(r.getBuffer().toString(), args); } catch(IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Generates a delete by id xml string * @param id ID that has not already been xml escaped * @param args The attributes of the delete tag */ public static String deleteById(String id, String... args) { try { StringWriter r = new StringWriter(); XML.writeXML(r, "id", id); return delete(r.getBuffer().toString(), args); } catch(IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Generates a delete xml string * @param val text that has not already been xml escaped * @param args 0 and Even numbered args are params, Odd numbered args are XML escaped values. */ private static String delete(String val, String... args) { try { StringWriter r = new StringWriter(); XML.writeUnescapedXML(r, "delete", val, (Object[]) args); return r.getBuffer().toString(); } catch(IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Helper that returns an <optimize> String with * optional key/val pairs. * * @param args 0 and Even numbered args are params, Odd numbered args are values. */ public static String optimize(String... args) { return simpleTag("optimize", args); } public static String simpleTag(String tag, String... args) { try { StringWriter r = new StringWriter(); // this is annoying if (null == args || 0 == args.length) { XML.writeXML(r, tag, null); } else { XML.writeXML(r, tag, null, (Object[])args); } return r.getBuffer().toString(); } catch (IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Helper that returns an <commit> String with * optional key/val pairs. * * @param args 0 and Even numbered args are params, Odd numbered args are values. */ public static String commit(String... args) { return simpleTag("commit", args); } /** Reloads the core */ abstract public void reload() throws Exception; /** * Processes an "update" (add, commit or optimize) and * returns the response as a String. * * This method does NOT commit after the request. * * @param xml The XML of the update * @return The XML response to the update */ abstract public String update(String xml); /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String validateUpdate(String xml) throws SAXException { return checkUpdateStatus(xml, "0"); } /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String validateErrorUpdate(String xml) throws SAXException { try { return checkUpdateStatus(xml, "1"); } catch (SolrException e) { // return ((SolrException)e).getMessage(); return null; // success } } /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String checkUpdateStatus(String xml, String code) throws SAXException { try { String res = update(xml); String valid = validateXPath(res, "//int[@name='status']="+code ); return (null == valid) ? null : res; } catch (XPathExpressionException e) { throw new RuntimeException ("?!? static xpath has bug?", e); } } }