/* * 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.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.XML; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.search.ReturnFields; import org.apache.solr.search.SolrReturnFields; import static org.apache.solr.common.params.CommonParams.NAME; /** * @lucene.internal */ public class XMLWriter extends TextResponseWriter { public static float CURRENT_VERSION=2.2f; private static final char[] XML_START1="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".toCharArray(); private static final char[] XML_STYLESHEET="<?xml-stylesheet type=\"text/xsl\" href=\"".toCharArray(); private static final char[] XML_STYLESHEET_END="\"?>\n".toCharArray(); /* private static final char[] XML_START2_SCHEMA=( "<response xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +" xsi:noNamespaceSchemaLocation=\"http://pi.cnet.com/cnet-search/response.xsd\">\n" ).toCharArray(); ***/ private static final char[] XML_START2_NOSCHEMA=("<response>\n").toCharArray(); final int version; public static void writeResponse(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { XMLWriter xmlWriter = null; try { xmlWriter = new XMLWriter(writer, req, rsp); xmlWriter.writeResponse(); } finally { xmlWriter.close(); } } public XMLWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) { super(writer, req, rsp); String version = req.getParams().get(CommonParams.VERSION); float ver = version==null? CURRENT_VERSION : Float.parseFloat(version); this.version = (int)(ver*1000); if( this.version < 2200 ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "XMLWriter does not support version: "+version ); } } public void writeResponse() throws IOException { writer.write(XML_START1); String stylesheet = req.getParams().get("stylesheet"); if (stylesheet != null && stylesheet.length() > 0) { writer.write(XML_STYLESHEET); XML.escapeAttributeValue(stylesheet, writer); writer.write(XML_STYLESHEET_END); } /* String noSchema = req.getParams().get("noSchema"); // todo - change when schema becomes available? if (false && noSchema == null) writer.write(XML_START2_SCHEMA); else writer.write(XML_START2_NOSCHEMA); ***/ writer.write(XML_START2_NOSCHEMA); // dump response values Boolean omitHeader = req.getParams().getBool(CommonParams.OMIT_HEADER); if(omitHeader != null && omitHeader) rsp.removeResponseHeader(); final NamedList<?> lst = rsp.getValues(); int sz = lst.size(); int start=0; for (int i=start; i<sz; i++) { writeVal(lst.getName(i),lst.getVal(i)); } writer.write("\n</response>\n"); } /** Writes the XML attribute name/val. A null val means that the attribute is missing. */ private void writeAttr(String name, String val) throws IOException { writeAttr(name, val, true); } public void writeAttr(String name, String val, boolean escape) throws IOException{ if (val != null) { writer.write(' '); writer.write(name); writer.write("=\""); if(escape){ XML.escapeAttributeValue(val, writer); } else { writer.write(val); } writer.write('"'); } } void startTag(String tag, String name, boolean closeTag) throws IOException { if (doIndent) indent(); writer.write('<'); writer.write(tag); if (name!=null) { writeAttr(NAME, name); if (closeTag) { writer.write("/>"); } else { writer.write(">"); } } else { if (closeTag) { writer.write("/>"); } else { writer.write('>'); } } } @Override public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException { if (doIndent) indent(); writer.write("<result"); writeAttr(NAME, name); writeAttr("numFound",Long.toString(numFound)); writeAttr("start",Long.toString(start)); if(maxScore!=null) { writeAttr("maxScore",Float.toString(maxScore)); } writer.write(">"); incLevel(); } /** * The SolrDocument should already have multivalued fields implemented as * Collections -- this will not rewrite to <arr> */ @Override public void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx ) throws IOException { startTag("doc", name, false); incLevel(); for (String fname : doc.getFieldNames()) { if (returnFields!= null && !returnFields.wantsField(fname)) { continue; } Object val = doc.getFieldValue(fname); if( "_explain_".equals( fname ) ) { System.out.println( val ); } writeVal(fname, val); } if(doc.hasChildDocuments()) { for(SolrDocument childDoc : doc.getChildDocuments()) { writeSolrDocument(null, childDoc, new SolrReturnFields(), idx); } } decLevel(); writer.write("</doc>"); } @Override public void writeEndDocumentList() throws IOException { decLevel(); if (doIndent) indent(); writer.write("</result>"); } // // Generic compound types // @Override public void writeNamedList(String name, NamedList val) throws IOException { int sz = val.size(); startTag("lst", name, sz<=0); incLevel(); for (int i=0; i<sz; i++) { writeVal(val.getName(i),val.getVal(i)); } decLevel(); if (sz > 0) { if (doIndent) indent(); writer.write("</lst>"); } } @Override public void writeMap(String name, Map map, boolean excludeOuter, boolean isFirstVal) throws IOException { int sz = map.size(); if (!excludeOuter) { startTag("lst", name, sz<=0); incLevel(); } for (Map.Entry entry : (Set<Map.Entry>)map.entrySet()) { Object k = entry.getKey(); Object v = entry.getValue(); // if (sz<indentThreshold) indent(); writeVal( null == k ? null : k.toString(), v); } if (!excludeOuter) { decLevel(); if (sz > 0) { if (doIndent) indent(); writer.write("</lst>"); } } } @Override public void writeArray(String name, Object[] val) throws IOException { writeArray(name, Arrays.asList(val).iterator()); } @Override public void writeArray(String name, Iterator iter) throws IOException { if( iter.hasNext() ) { startTag("arr", name, false ); incLevel(); while( iter.hasNext() ) { writeVal(null, iter.next()); } decLevel(); if (doIndent) indent(); writer.write("</arr>"); } else { startTag("arr", name, true ); } } // // Primitive types // @Override public void writeNull(String name) throws IOException { writePrim("null",name,"",false); } @Override public void writeStr(String name, String val, boolean escape) throws IOException { writePrim("str",name,val,escape); } @Override public void writeInt(String name, String val) throws IOException { writePrim("int",name,val,false); } @Override public void writeLong(String name, String val) throws IOException { writePrim("long",name,val,false); } @Override public void writeBool(String name, String val) throws IOException { writePrim("bool",name,val,false); } @Override public void writeFloat(String name, String val) throws IOException { writePrim("float",name,val,false); } @Override public void writeFloat(String name, float val) throws IOException { writeFloat(name,Float.toString(val)); } @Override public void writeDouble(String name, String val) throws IOException { writePrim("double",name,val,false); } @Override public void writeDouble(String name, double val) throws IOException { writeDouble(name,Double.toString(val)); } @Override public void writeDate(String name, String val) throws IOException { writePrim("date",name,val,false); } // // OPT - specific writeInt, writeFloat, methods might be faster since // there would be less write calls (write("<int name=\"" + name + ... + </int>) // private void writePrim(String tag, String name, String val, boolean escape) throws IOException { int contentLen = val==null ? 0 : val.length(); startTag(tag, name, contentLen==0); if (contentLen==0) return; if (escape) { XML.escapeCharData(val,writer); } else { writer.write(val,0,contentLen); } writer.write('<'); writer.write('/'); writer.write(tag); writer.write('>'); } }