From 8f9eeb1e2cc0b508b9dc485a7ff9e0737fd210e8 Mon Sep 17 00:00:00 2001 From: Noble Paul Date: Sat, 9 Jun 2018 23:35:53 +1000 Subject: [PATCH] SOLR-12449: Response /autoscaling/diagnostics shows improper json --- .../org/apache/solr/response/JSONWriter.java | 624 ++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 solr/core/src/java/org/apache/solr/response/JSONWriter.java diff --git a/solr/core/src/java/org/apache/solr/response/JSONWriter.java b/solr/core/src/java/org/apache/solr/response/JSONWriter.java new file mode 100644 index 00000000000..fb615cc4aa7 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/response/JSONWriter.java @@ -0,0 +1,624 @@ +/* + * 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.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.solr.common.IteratorWriter; +import org.apache.solr.common.MapWriter; +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.search.ReturnFields; + +public class JSONWriter extends TextResponseWriter { + public static final String JSON_NL_MAP="map"; + public static final String JSON_NL_FLAT="flat"; + public static final String JSON_NL_ARROFARR="arrarr"; + public static final String JSON_NL_ARROFMAP="arrmap"; + public static final String JSON_NL_ARROFNTV="arrntv"; + static final String JSON_NL_STYLE="json.nl"; + static final int JSON_NL_STYLE_COUNT = 5; // for use by JSONWriterTest + static final String JSON_WRAPPER_FUNCTION="json.wrf"; + private static char[] hexdigits = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + final protected String namedListStyle; + protected String wrapperFunction; + + public JSONWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) { + this(writer, req, rsp, + req.getParams().get(JSON_WRAPPER_FUNCTION), + req.getParams().get(JSON_NL_STYLE, JSON_NL_FLAT).intern()); + } + + public JSONWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp, + String wrapperFunction, String namedListStyle) { + super(writer, req, rsp); + this.wrapperFunction = wrapperFunction; + this.namedListStyle = namedListStyle; + } + private JSONWriter(Writer writer, boolean intend, String namedListStyle) throws IOException { + super(writer, intend); + this.namedListStyle = namedListStyle; + + } + + /**Strictly for testing only + */ + public static void write(Writer writer, boolean intend, String namedListStyle, Object val) throws IOException { + JSONWriter jw = new JSONWriter(writer, intend, namedListStyle); + jw.writeVal(null, val); + jw.close(); + + } + + protected static void unicodeEscape(Appendable out, int ch) throws IOException { + out.append('\\'); + out.append('u'); + out.append(hexdigits[(ch>>>12) ]); + out.append(hexdigits[(ch>>>8) & 0xf]); + out.append(hexdigits[(ch>>>4) & 0xf]); + out.append(hexdigits[(ch) & 0xf]); + } + + public void writeResponse() throws IOException { + if(wrapperFunction!=null) { + writer.write(wrapperFunction + "("); + } + writeNamedList(null, rsp.getValues()); + if(wrapperFunction!=null) { + writer.write(')'); + } + writer.write('\n'); // ending with a newline looks much better from the command line + } + + protected void writeKey(String fname, boolean needsEscaping) throws IOException { + writeStr(null, fname, needsEscaping); + writer.write(':'); + } + + /** Represents a NamedList directly as a JSON Object (essentially a Map) + * Map null to "" and name mangle any repeated keys to avoid repeats in the + * output. + */ + protected void writeNamedListAsMapMangled(String name, NamedList val) throws IOException { + int sz = val.size(); + writeMapOpener(sz); + incLevel(); + + // In JSON objects (maps) we can't have null keys or duplicates... + // map null to "" and append a qualifier to duplicates. + // + // a=123,a=456 will be mapped to {a=1,a__1=456} + // Disad: this is ambiguous since a real key could be called a__1 + // + // Another possible mapping could aggregate multiple keys to an array: + // a=123,a=456 maps to a=[123,456] + // Disad: this is ambiguous with a real single value that happens to be an array + // + // Both of these mappings have ambiguities. + HashMap repeats = new HashMap<>(4); + + boolean first=true; + for (int i=0; i {"a":1,"bar":"foo","":3,"":null} + protected void writeNamedListAsMapWithDups(String name, NamedList val) throws IOException { + int sz = val.size(); + writeMapOpener(sz); + incLevel(); + + for (int i=0; i [{"a":1},{"b":2},3,null] + protected void writeNamedListAsArrMap(String name, NamedList val) throws IOException { + int sz = val.size(); + indent(); + writeArrayOpener(sz); + incLevel(); + + boolean first=true; + for (int i=0; i [["a",1],["b",2],[null,3],[null,null]] + protected void writeNamedListAsArrArr(String name, NamedList val) throws IOException { + int sz = val.size(); + indent(); + writeArrayOpener(sz); + incLevel(); + + boolean first=true; + for (int i=0; i ["a",1,"b",2,null,3,null,null] + protected void writeNamedListAsFlat(String name, NamedList val) throws IOException { + int sz = val.size(); + writeArrayOpener(sz*2); + incLevel(); + + for (int i=0; i 0 ) { + writeArraySeparator(); + } + + indent(); + writeMapOpener(doc.size()); + incLevel(); + + boolean first=true; + for (String fname : doc.getFieldNames()) { + if (returnFields!= null && !returnFields.wantsField(fname)) { + continue; + } + + if (first) { + first=false; + } + else { + writeMapSeparator(); + } + + indent(); + writeKey(fname, true); + Object val = doc.getFieldValue(fname); + writeVal(fname, val); + } + + if(doc.hasChildDocuments()) { + if(first == false) { + writeMapSeparator(); + indent(); + } + writeKey("_childDocuments_", true); + writeArrayOpener(doc.getChildDocumentCount()); + List childDocs = doc.getChildDocuments(); + for(int i=0; i '#' && ch != '\\' && ch < '\u2028') || ch == ' ') { // fast path + writer.write(ch); + continue; + } + switch(ch) { + case '"': + case '\\': + writer.write('\\'); + writer.write(ch); + break; + case '\r': writer.write('\\'); writer.write('r'); break; + case '\n': writer.write('\\'); writer.write('n'); break; + case '\t': writer.write('\\'); writer.write('t'); break; + case '\b': writer.write('\\'); writer.write('b'); break; + case '\f': writer.write('\\'); writer.write('f'); break; + case '\u2028': // fallthrough + case '\u2029': + unicodeEscape(writer,ch); + break; + // case '/': + default: { + if (ch <= 0x1F) { + unicodeEscape(writer,ch); + } else { + writer.write(ch); + } + } + } + } + + writer.write('"'); + } else { + writer.write('"'); + writer.write(val); + writer.write('"'); + } + } + + @Override + public void writeIterator(IteratorWriter val) throws IOException { + writeArrayOpener(-1); + incLevel(); + val.writeIter(new IteratorWriter.ItemWriter() { + boolean first = true; + + @Override + public IteratorWriter.ItemWriter add(Object o) throws IOException { + if (!first) { + JSONWriter.this.indent(); + JSONWriter.this.writeArraySeparator(); + } + JSONWriter.this.writeVal(null, o); + first = false; + return this; + } + }); + decLevel(); + writeArrayCloser(); + } + + @Override + public void writeMap(MapWriter val) + throws IOException { + writeMapOpener(-1); + incLevel(); + + val.writeMap(new MapWriter.EntryWriter() { + boolean isFirst = true; + + @Override + public MapWriter.EntryWriter put(String k, Object v) throws IOException { + if (isFirst) { + isFirst = false; + } else { + JSONWriter.this.writeMapSeparator(); + } + if (doIndent) JSONWriter.this.indent(); + JSONWriter.this.writeKey(k, true); + JSONWriter.this.writeVal(k, v); + return this; + } + }); + decLevel(); + writeMapCloser(); + } + + @Override + public void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException { + if (!excludeOuter) { + writeMapOpener(val.size()); + incLevel(); + isFirstVal=true; + } + + boolean doIndent = excludeOuter || val.size() > 1; + + for (Map.Entry entry : (Set)val.entrySet()) { + Object e = entry.getKey(); + String k = e==null ? "" : e.toString(); + Object v = entry.getValue(); + + if (isFirstVal) { + isFirstVal=false; + } else { + writeMapSeparator(); + } + + if (doIndent) indent(); + writeKey(k,true); + writeVal(k,v); + } + + if (!excludeOuter) { + decLevel(); + writeMapCloser(); + } + } + + @Override + public void writeArray(String name, List l) throws IOException { + writeArrayOpener(l.size()); + writeJsonIter(l.iterator()); + writeArrayCloser(); + } + + @Override + public void writeArray(String name, Iterator val) throws IOException { + writeArrayOpener(-1); // no trivial way to determine array size + writeJsonIter(val); + writeArrayCloser(); + } + + private void writeJsonIter(Iterator val) throws IOException { + incLevel(); + boolean first=true; + while( val.hasNext() ) { + if( !first ) indent(); + writeVal(null, val.next()); + if( val.hasNext() ) { + writeArraySeparator(); + } + first=false; + } + decLevel(); + } + + // + // Primitive types + // + @Override + public void writeNull(String name) throws IOException { + writer.write("null"); + } + + @Override + public void writeInt(String name, String val) throws IOException { + writer.write(val); + } + + @Override + public void writeLong(String name, String val) throws IOException { + writer.write(val); + } + + @Override + public void writeBool(String name, String val) throws IOException { + writer.write(val); + } + + @Override + public void writeFloat(String name, String val) throws IOException { + writer.write(val); + } + + @Override + public void writeDouble(String name, String val) throws IOException { + writer.write(val); + } + + @Override + public void writeDate(String name, String val) throws IOException { + writeStr(name, val, false); + } + +}