Package org.apache.solr.response

Source Code of org.apache.solr.response.XMLWriter$DocumentListInfo

/**
* 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.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
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.SolrIndexSearcher;
import org.apache.solr.search.DocList;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.TextField;

import java.io.Writer;
import java.io.IOException;
import java.util.*;

import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.Document;
/**
* @version $Id: XMLWriter.java 1075192 2011-02-28 00:50:09Z uschindler $
*/
final public class XMLWriter {

  public static float CURRENT_VERSION=2.2f;

  //
  // static thread safe part
  //
  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();


  public static void writeResponse(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {

    String ver = req.getParams().get(CommonParams.VERSION);

    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);

    // create an instance for each request to handle
    // non-thread safe stuff (indentation levels, etc)
    // and to encapsulate writer, schema, and searcher so
    // they don't have to be passed around in every function.
    //
    XMLWriter xw = new XMLWriter(writer, req.getSchema(), req, ver);
    xw.defaultFieldList = rsp.getReturnFields();

    String indent = req.getParams().get("indent");
    if (indent != null) {
      if ("".equals(indent) || "off".equals(indent)) {
        xw.setIndent(false);
      } else {
        xw.setIndent(true);
      }
    }

    // dump response values
    NamedList lst = rsp.getValues();
    Boolean omitHeader = req.getParams().getBool(CommonParams.OMIT_HEADER);
    if(omitHeader != null && omitHeader) lst.remove("responseHeader");
    int sz = lst.size();
    int start=0;

    // special case the response header if the version is 2.1 or less   
    if (xw.version<=2100 && sz>0) {
      Object header = lst.getVal(0);
      if (header instanceof NamedList && "responseHeader".equals(lst.getName(0))) {
        writer.write("<responseHeader>");
        xw.incLevel();
        NamedList nl = (NamedList)header;
        for (int i=0; i<nl.size(); i++) {
          String name = nl.getName(i);
          Object val = nl.getVal(i);
          if ("status".equals(name) || "QTime".equals(name)) {
            xw.writePrim(name,null,val.toString(),false);
          } else {
            xw.writeVal(name,val);
          }
        }
        xw.decLevel();
        writer.write("</responseHeader>");
        start=1;
      }
    }

    for (int i=start; i<sz; i++) {
      xw.writeVal(lst.getName(i),lst.getVal(i));
    }

    writer.write("\n</response>\n");
  }


  ////////////////////////////////////////////////////////////
  // request instance specific (non-static, not shared between threads)
  ////////////////////////////////////////////////////////////

  private final Writer writer;
  private final IndexSchema schema; // needed to write fields of docs
  private final SolrQueryRequest request; // the request

  private int level;
  private boolean defaultIndent=false;
  private boolean doIndent=false;

  // fieldList... the set of fields to return for each document
  private Set<String> defaultFieldList;


  // if a list smaller than this threshold is encountered, elements
  // will be written on the same line.
  // maybe constructed types should always indent first?
  private final int indentThreshold=0;

  final int version;


  // temporary working objects...
  // be careful not to use these recursively...
  private final ArrayList tlst = new ArrayList();
  private final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.US);
  private final StringBuilder sb = new StringBuilder();

  public XMLWriter(Writer writer, IndexSchema schema, SolrQueryRequest req, String version) {
    this.writer = writer;
    this.schema = schema;
    this.request = req;
   
    float ver = version==null? CURRENT_VERSION : Float.parseFloat(version);
    this.version = (int)(ver*1000);
  }

  //
  // Functions to manipulate the current logical nesting level.
  // Any indentation will be partially based on level.
  //
  public void setLevel(int level) { this.level = level; }
  public int level() { return level; }
  public int incLevel() { return ++level; }
  public int decLevel() { return --level; }
  public void setIndent(boolean doIndent) {
    this.doIndent = doIndent;
    defaultIndent = doIndent;
  }

  /** Writes the XML attribute name/val. A null val means that the attribute is missing. */
  public 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('"');
    }
  }

  /**Writes a tag with attributes
   *
   * @param tag
   * @param attributes
   * @param closeTag
   * @param escape
   * @throws IOException
   */
  public void startTag(String tag, Map<String,String> attributes, boolean closeTag, boolean escape) throws IOException {
    if (doIndent) indent();
    writer.write('<');
    writer.write(tag);
    if(!attributes.isEmpty()) {
      for (Map.Entry<String, String> entry : attributes.entrySet()) {
        writeAttr(entry.getKey(), entry.getValue(), escape);
      }
    }
    if (closeTag) {
      writer.write("/>");
    } else {
      writer.write('>');
    }
  }

  /**Write a complete tag w/ attributes and cdata (the cdata is not enclosed in $lt;!CDATA[]!&gt;
   * @param tag
   * @param attributes
   * @param cdata
   * @param escapeCdata
   * @param escapeAttr
   * @throws IOException
   */
  public void writeCdataTag(String tag, Map<String,String> attributes, String cdata, boolean escapeCdata, boolean escapeAttr) throws IOException {
    if (doIndent) indent();
    writer.write('<');
    writer.write(tag);
    if (!attributes.isEmpty()) {
      for (Map.Entry<String, String> entry : attributes.entrySet()) {
        writeAttr(entry.getKey(), entry.getValue(), escapeAttr);
      }
    }
    writer.write('>');
    if (cdata != null && cdata.length() > 0) {
      if (escapeCdata) {
        XML.escapeCharData(cdata, writer);
      } else {
        writer.write(cdata, 0, cdata.length());
      }
    }
    writer.write("</");
    writer.write(tag);
    writer.write('>');
  }



  public 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('>');
      }
    }
  }


  // indent up to 40 spaces
  static final char[] indentChars = new char[81];
  static {
    Arrays.fill(indentChars,' ');
    indentChars[0] = '\n'// start with a newline
  }

  public void indent() throws IOException {
     indent(level);
  }

  public void indent(int lev) throws IOException {
    writer.write(indentChars, 0, Math.min((lev<<1)+1, indentChars.length));
  }

  private static final Comparator fieldnameComparator = new Comparator() {
    public int compare(Object o, Object o1) {
      Fieldable f1 = (Fieldable)o; Fieldable f2 = (Fieldable)o1;
      int cmp = f1.name().compareTo(f2.name());
      return cmp;
      // note - the sort is stable, so this should not have affected the ordering
      // of fields with the same name w.r.t eachother.
    }
  };

  public final void writeDoc(String name, Document doc, Set<String> returnFields, float score, boolean includeScore) throws IOException {
    startTag("doc", name, false);
    incLevel();

    if (includeScore) {
      writeFloat("score", score);
    }


    // Lucene Documents have multivalued types as multiple fields
    // with the same name.
    // The XML needs to represent these as
    // an array.  The fastest way to detect multiple fields
    // with the same name is to sort them first.


    // using global tlst here, so we shouldn't call any other
    // function that uses it until we are done.
    tlst.clear();
    for (Object obj : doc.getFields()) {
      Fieldable ff = (Fieldable)obj;
      // skip this field if it is not a field to be returned.
      if (returnFields!=null && !returnFields.contains(ff.name())) {
        continue;
      }
      tlst.add(ff);
    }
    Collections.sort(tlst, fieldnameComparator);

    int sz = tlst.size();
    int fidx1 = 0, fidx2 = 0;
    while (fidx1 < sz) {
      Fieldable f1 = (Fieldable)tlst.get(fidx1);
      String fname = f1.name();

      // find the end of fields with this name
      fidx2 = fidx1+1;
      while (fidx2 < sz && fname.equals(((Fieldable)tlst.get(fidx2)).name()) ) {
        fidx2++;
      }

      /***
      // more efficient to use getFieldType instead of
      // getField since that way dynamic fields won't have
      // to create a SchemaField on the fly.
      FieldType ft = schema.getFieldType(fname);
      ***/

      SchemaField sf = schema.getFieldOrNull(fname);
      if( sf == null ) {
        sf = new SchemaField( fname, new TextField() );
      }
      if (fidx1+1 == fidx2) {
        // single field value
        if (version>=2100 && sf.multiValued()) {
          startTag("arr",fname,false);
          doIndent=false;
          sf.write(this, null, f1);
          writer.write("</arr>");
          doIndent=defaultIndent;
        } else {
          sf.write(this, f1.name(), f1);
        }
      } else {
        // multiple fields with same name detected

        startTag("arr",fname,false);
        incLevel();
        doIndent=false;
        int cnt=0;
        for (int i=fidx1; i<fidx2; i++) {
          if (defaultIndent && ++cnt==4) { // only indent every 4th item
            indent();
            cnt=0;
          }
          sf.write(this, null, (Fieldable)tlst.get(i));
        }
        decLevel();
        // if (doIndent) indent();
        writer.write("</arr>");
        // doIndent=true;
        doIndent=defaultIndent;
      }
      fidx1 = fidx2;
    }

    decLevel();
    if (doIndent) indent();
    writer.write("</doc>");
  }

  /**
   * @since solr 1.3
   */
  final void writeDoc(String name, SolrDocument doc, Set<String> returnFields, boolean includeScore) throws IOException {
    startTag("doc", name, false);
    incLevel();

    if (includeScore && returnFields != null ) {
      returnFields.add( "score" );
    }

    for (String fname : doc.getFieldNames()) {
      if (returnFields!=null && !returnFields.contains(fname)) {
        continue;
      }
      Object val = doc.getFieldValue(fname);

      if (val instanceof Collection) {
        writeVal(fname, val);
      } else {
        // single valued... figure out if we should put <arr> tags around it anyway
        SchemaField sf = schema.getFieldOrNull(fname);
        if (version>=2100 && sf!=null && sf.multiValued()) {
          startTag("arr",fname,false);
          doIndent=false;
          writeVal(fname, val);
          writer.write("</arr>");
          doIndent=defaultIndent;         
        } else {
          writeVal(fname, val);         
        }
      }
    }

    decLevel();
    if (doIndent) indent();
    writer.write("</doc>");
  }


  private static interface DocumentListInfo {
    Float getMaxScore();
    int getCount();
    long getNumFound();
    long getStart();
    void writeDocs( boolean includeScore, Set<String> fields ) throws IOException;
  }

  private final void writeDocuments(
      String name,
      DocumentListInfo docs,
      Set<String> fields) throws IOException
  {
    boolean includeScore=false;
    if (fields!=null) {
      includeScore = fields.contains("score");
      if (fields.size()==0 || (fields.size()==1 && includeScore) || fields.contains("*")) {
        fields=null// null means return all stored fields
      }
    }
   
    int sz=docs.getCount();
    if (doIndent) indent();
   
    writer.write("<result");
    writeAttr("name",name);
    writeAttr("numFound",Long.toString(docs.getNumFound()))// TODO: change to long
    writeAttr("start",Long.toString(docs.getStart()));        // TODO: change to long
    if (includeScore && docs.getMaxScore()!=null) {
      writeAttr("maxScore",Float.toString(docs.getMaxScore()));
    }
    if (sz==0) {
      writer.write("/>");
      return;
    } else {
      writer.write('>');
    }

    incLevel();
    docs.writeDocs(includeScore, fields);
    decLevel();

    if (doIndent) indent();
    writer.write("</result>");
  }
 
  public final void writeSolrDocumentList(String name, final SolrDocumentList docs, Set<String> fields) throws IOException
  {
    this.writeDocuments( name, new DocumentListInfo()
    { 
      public int getCount() {
        return docs.size();
      }
     
      public Float getMaxScore() {
        return docs.getMaxScore();
      }

      public long getNumFound() {
        return docs.getNumFound();
      }

      public long getStart() {
        return docs.getStart();
      }

      public void writeDocs(boolean includeScore, Set<String> fields) throws IOException {
        for( SolrDocument doc : docs ) {
          writeDoc(null, doc, fields, includeScore);
        }
      }
    }, fields );
  }

  public final void writeDocList(String name, final DocList ids, Set<String> fields) throws IOException
  {
    this.writeDocuments( name, new DocumentListInfo()
    { 
      public int getCount() {
        return ids.size();
      }
     
      public Float getMaxScore() {
        return ids.maxScore();
      }

      public long getNumFound() {
        return ids.matches();
      }

      public long getStart() {
        return ids.offset();
      }

      public void writeDocs(boolean includeScore, Set<String> fields) throws IOException {
        SolrIndexSearcher searcher = request.getSearcher();
        DocIterator iterator = ids.iterator();
        int sz = ids.size();
        includeScore = includeScore && ids.hasScores();
        for (int i=0; i<sz; i++) {
          int id = iterator.nextDoc();
          Document doc = searcher.doc(id, fields);
          writeDoc(null, doc, fields, (includeScore ? iterator.score() : 0.0f), includeScore);
        }
      }
    }, fields );
  }


  public void writeVal(String name, Object val) throws IOException {

    // if there get to be enough types, perhaps hashing on the type
    // to get a handler might be faster (but types must be exact to do that...)

    // go in order of most common to least common
    if (val==null) {
      writeNull(name);
    } else if (val instanceof String) {
      writeStr(name, (String)val);
    } else if (val instanceof Integer) {
      // it would be slower to pass the int ((Integer)val).intValue()
      writeInt(name, val.toString());
    } else if (val instanceof Boolean) {
      // could be optimized... only two vals
      writeBool(name, val.toString());
    } else if (val instanceof Long) {
      writeLong(name, val.toString());
    } else if (val instanceof Date) {
      writeDate(name,(Date)val);
    } else if (val instanceof Float) {
      // we pass the float instead of using toString() because
      // it may need special formatting. same for double.
      writeFloat(name, ((Float)val).floatValue());
    } else if (val instanceof Double) {
      writeDouble(name, ((Double)val).doubleValue());
    } else if (val instanceof Document) {
      writeDoc(name, (Document)val, defaultFieldList, 0.0f, false);
    } else if (val instanceof DocList) {
      // requires access to IndexReader
      writeDocList(name, (DocList)val, defaultFieldList);
    }else if (val instanceof SolrDocumentList) {
        // requires access to IndexReader
      writeSolrDocumentList(name, (SolrDocumentList)val, defaultFieldList)
    }else if (val instanceof DocSet) {
      // how do we know what fields to read?
      // todo: have a DocList/DocSet wrapper that
      // restricts the fields to write...?
    } else if (val instanceof Map) {
      writeMap(name, (Map)val);
    } else if (val instanceof NamedList) {
      writeNamedList(name, (NamedList)val);
    } else if (val instanceof Iterable) {
      writeArray(name,((Iterable)val).iterator());
    } else if (val instanceof Object[]) {
      writeArray(name,(Object[])val);
    } else if (val instanceof Iterator) {
      writeArray(name,(Iterator)val);
    } else {
      // default...
      writeStr(name, val.getClass().getName() + ':' + val.toString());
    }
  }

  //
  // Generic compound types
  //

  public void writeNamedList(String name, NamedList val) throws IOException {
    int sz = val.size();
    startTag("lst", name, sz<=0);

    if (sz<indentThreshold) {
      doIndent=false;
    }

    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>");
    }
  }

 
  /**
   * writes a Map in the same format as a NamedList, using the
   * stringification of the key Object when it's non-null.
   *
   * @param name
   * @param map
   * @throws IOException
   * @see SolrQueryResponse Note on Returnable Data
   */
  public void writeMap(String name, Map<Object,Object> map) throws IOException {
    int sz = map.size();
    startTag("lst", name, sz<=0);
    incLevel();
    for (Map.Entry<Object,Object> entry : map.entrySet()) {
      Object k = entry.getKey();
      Object v = entry.getValue();
      // if (sz<indentThreshold) indent();
      writeVal( null == k ? null : k.toString(), v);
    }
    decLevel();
    if (sz > 0) {
      if (doIndent) indent();
      writer.write("</lst>");
    }
  }

  public void writeArray(String name, Object[] val) throws IOException {
    writeArray(name, Arrays.asList(val).iterator());
  }

  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
  //

  public void writeNull(String name) throws IOException {
    writePrim("null",name,"",false);
  }

  public void writeStr(String name, String val) throws IOException {
    writePrim("str",name,val,true);
  }

  public void writeInt(String name, String val) throws IOException {
    writePrim("int",name,val,false);
  }

  public void writeInt(String name, int val) throws IOException {
    writeInt(name,Integer.toString(val));
  }

  public void writeLong(String name, String val) throws IOException {
    writePrim("long",name,val,false);
  }

  public void writeLong(String name, long val) throws IOException {
    writeLong(name,Long.toString(val));
  }

  public void writeBool(String name, String val) throws IOException {
    writePrim("bool",name,val,false);
  }

  public void writeBool(String name, boolean val) throws IOException {
    writeBool(name,Boolean.toString(val));
  }

  public void writeShort(String name, String val) throws IOException {
    writePrim("short",name,val,false);
  }

  public void writeShort(String name, short val) throws IOException {
    writeInt(name,Short.toString(val));
  }


  public void writeByte(String name, String val) throws IOException {
    writePrim("byte",name,val,false);
  }

  public void writeByte(String name, byte val) throws IOException {
    writeInt(name,Byte.toString(val));
  }


  public void writeFloat(String name, String val) throws IOException {
    writePrim("float",name,val,false);
  }

  public void writeFloat(String name, float val) throws IOException {
    writeFloat(name,Float.toString(val));
  }

  public void writeDouble(String name, String val) throws IOException {
    writePrim("double",name,val,false);
  }

  public void writeDouble(String name, double val) throws IOException {
    writeDouble(name,Double.toString(val));
  }

  public void writeDate(String name, Date val) throws IOException {
    // using a stringBuilder for numbers can be nice since
    // a temporary string isn't used (it's added directly to the
    // builder's buffer.

    cal.setTime(val);

    sb.setLength(0);
    int i = cal.get(Calendar.YEAR);
    sb.append(i);
    sb.append('-');
    i = cal.get(Calendar.MONTH) + 1// 0 based, so add 1
    if (i<10) sb.append('0');
    sb.append(i);
    sb.append('-');
    i=cal.get(Calendar.DAY_OF_MONTH);
    if (i<10) sb.append('0');
    sb.append(i);
    sb.append('T');
    i=cal.get(Calendar.HOUR_OF_DAY); // 24 hour time format
    if (i<10) sb.append('0');
    sb.append(i);
    sb.append(':');
    i=cal.get(Calendar.MINUTE);
    if (i<10) sb.append('0');
    sb.append(i);
    sb.append(':');
    i=cal.get(Calendar.SECOND);
    if (i<10) sb.append('0');
    sb.append(i);
    i=cal.get(Calendar.MILLISECOND);
    if (i != 0) {
      sb.append('.');
      if (i<100) sb.append('0');
      if (i<10) sb.append('0');
      sb.append(i);

      // handle canonical format specifying fractional
      // seconds shall not end in '0'.  Given the slowness of
      // integer div/mod, simply checking the last character
      // is probably the fastest way to check.
      int lastIdx = sb.length()-1;
      if (sb.charAt(lastIdx)=='0') {
        lastIdx--;
        if (sb.charAt(lastIdx)=='0') {
          lastIdx--;
        }
        sb.setLength(lastIdx+1);
      }

    }
    sb.append('Z');
    writeDate(name, sb.toString());
  }

  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>)
  //
  public void writePrim(String tag, String name, String val, boolean escape) throws IOException {
    // OPT - we could use a temp char[] (or a StringBuilder) and if the
    // size was small enough to fit (if escape==false we can calc exact size)
    // then we could put things directly in the temp buf.
    // need to see what percent of CPU this takes up first though...
    // Could test a reusable StringBuilder...

    // is this needed here???
    // Only if a fieldtype calls writeStr or something
    // with a null val instead of calling writeNull
    /***
    if (val==null) {
      if (name==null) writer.write("<null/>");
      else writer.write("<null name=\"" + name + "/>");
    }
    ***/

    int contentLen=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(tag);
    writer.write('>');
  }


}
TOP

Related Classes of org.apache.solr.response.XMLWriter$DocumentListInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.