blob: 35aa8f113eafde7cd43670537a762b4f97eaedfa [file] [log] [blame]
/*
* 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.IteratorWriter;
import org.apache.solr.common.MapWriter;
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, Boolean numFoundExact) 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));
}
if (numFoundExact != null) {
writeAttr("numFoundExact", numFoundExact.toString());
}
writer.write(">");
incLevel();
}
@Override
@Deprecated
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 &lt;arr&gt;
*/
@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, @SuppressWarnings({"rawtypes"})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, MapWriter val) throws IOException {
// As the size is not known. So, always both startTag and endTag is written
// irrespective of number of entries in MapWriter
startTag("lst", name, false);
incLevel();
val.writeMap(new MapWriter.EntryWriter() {
@Override
public MapWriter.EntryWriter put(CharSequence k, Object v) throws IOException {
writeVal( null == k ? null : k.toString(), v);
return this;
}
});
decLevel();
if (doIndent) {
indent();
}
writer.write("</lst>");
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public void writeMap(String name, @SuppressWarnings({"rawtypes"})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, @SuppressWarnings({"rawtypes"})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 );
}
}
@Override
public void writeIterator(String name, IteratorWriter val) throws IOException {
// As the size is not known. So, always both startTag and endTag is written
// irrespective of number of entries in IteratorWriter
startTag("arr", name, false );
incLevel();
val.writeIter(new IteratorWriter.ItemWriter() {
@Override
public IteratorWriter.ItemWriter add(Object o) throws IOException {
writeVal(null, o);
return this;
}
});
decLevel();
if (doIndent) {
indent();
}
writer.write("</arr>");
}
//
// 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('>');
}
}