All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.response.SchemaXmlWriter Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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 static org.apache.solr.common.params.CommonParams.NAME;

import java.io.IOException;
import java.io.Writer;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.XML;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SimilarityFactory;
import org.apache.solr.search.ReturnFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @lucene.internal
 */
public class SchemaXmlWriter extends TextResponseWriter {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  private static final char[] XML_DECLARATION =
      "".toCharArray();
  private static final char[] MANAGED_SCHEMA_DO_NOT_EDIT_WARNING =
      "".toCharArray();

  private boolean emitManagedSchemaDoNotEditWarning = false;

  public void setEmitManagedSchemaDoNotEditWarning(boolean emitManagedSchemaDoNotEditWarning) {
    this.emitManagedSchemaDoNotEditWarning = emitManagedSchemaDoNotEditWarning;
  }

  public static void writeResponse(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp)
      throws IOException {
    SchemaXmlWriter schemaXmlWriter = null;
    try {
      schemaXmlWriter = new SchemaXmlWriter(writer, req, rsp);
      schemaXmlWriter.writeResponse();
    } finally {
      if (null != schemaXmlWriter) {
        schemaXmlWriter.close();
      }
    }
  }

  public SchemaXmlWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
    super(writer, req, rsp);
  }

  @SuppressWarnings({"unchecked"})
  public void writeResponse() throws IOException {

    writer.write(XML_DECLARATION);
    if (emitManagedSchemaDoNotEditWarning) {
      if (doIndent) {
        writer.write('\n');
      }
      writer.write(MANAGED_SCHEMA_DO_NOT_EDIT_WARNING);
    }

    Map schemaProperties =
        (Map) rsp.getValues().get(IndexSchema.SCHEMA);

    openStartTag(IndexSchema.SCHEMA);
    writeAttr(IndexSchema.NAME, schemaProperties.get(IndexSchema.NAME).toString());
    writeAttr(IndexSchema.VERSION, schemaProperties.get(IndexSchema.VERSION).toString());
    closeStartTag(false);
    incLevel();

    for (Map.Entry entry : schemaProperties.entrySet()) {
      String schemaPropName = entry.getKey();
      Object val = entry.getValue();
      if (schemaPropName.equals(IndexSchema.NAME) || schemaPropName.equals(IndexSchema.VERSION)) {
        continue;
      }
      if (schemaPropName.equals(IndexSchema.UNIQUE_KEY)) {
        openStartTag(IndexSchema.UNIQUE_KEY);
        closeStartTag(false);
        writer.write(val.toString());
        endTag(IndexSchema.UNIQUE_KEY, false);
      } else if (schemaPropName.equals(IndexSchema.SIMILARITY)) {
        writeSimilarity((SimpleOrderedMap) val);
      } else if (schemaPropName.equals(IndexSchema.FIELD_TYPES)) {
        writeFieldTypes((List>) val);
      } else if (schemaPropName.equals(IndexSchema.FIELDS)) {
        List> fieldPropertiesList = (List>) val;
        for (SimpleOrderedMap fieldProperties : fieldPropertiesList) {
          openStartTag(IndexSchema.FIELD);
          for (int fieldPropNum = 0; fieldPropNum < fieldProperties.size(); ++fieldPropNum) {
            writeAttr(
                fieldProperties.getName(fieldPropNum),
                fieldProperties.getVal(fieldPropNum).toString());
          }
          closeStartTag(true);
        }
      } else if (schemaPropName.equals(IndexSchema.DYNAMIC_FIELDS)) {
        List> dynamicFieldPropertiesList =
            (List>) val;
        for (SimpleOrderedMap dynamicFieldProperties : dynamicFieldPropertiesList) {
          openStartTag(IndexSchema.DYNAMIC_FIELD);
          for (int dynamicFieldPropNum = 0;
              dynamicFieldPropNum < dynamicFieldProperties.size();
              ++dynamicFieldPropNum) {
            writeAttr(
                dynamicFieldProperties.getName(dynamicFieldPropNum),
                dynamicFieldProperties.getVal(dynamicFieldPropNum).toString());
          }
          closeStartTag(true);
        }
      } else if (schemaPropName.equals(IndexSchema.COPY_FIELDS)) {
        List> copyFieldPropertiesList =
            (List>) val;
        for (SimpleOrderedMap copyFieldProperties : copyFieldPropertiesList) {
          openStartTag(IndexSchema.COPY_FIELD);
          for (int copyFieldPropNum = 0;
              copyFieldPropNum < copyFieldProperties.size();
              ++copyFieldPropNum) {
            writeAttr(
                copyFieldProperties.getName(copyFieldPropNum),
                copyFieldProperties.getVal(copyFieldPropNum).toString());
          }
          closeStartTag(true);
        }
      } else {
        log.warn("Unknown schema component '{}'", schemaPropName);
      }
    }
    decLevel();
    endTag(IndexSchema.SCHEMA);
  }

  @SuppressWarnings({"unchecked"})
  private void writeFieldTypes(List> fieldTypePropertiesList)
      throws IOException {
    for (SimpleOrderedMap fieldTypeProperties : fieldTypePropertiesList) {
      SimpleOrderedMap analyzerProperties = null;
      SimpleOrderedMap indexAnalyzerProperties = null;
      SimpleOrderedMap queryAnalyzerProperties = null;
      SimpleOrderedMap multiTermAnalyzerProperties = null;
      SimpleOrderedMap perFieldSimilarityProperties = null;
      openStartTag(IndexSchema.FIELD_TYPE);
      for (int fieldTypePropNum = 0;
          fieldTypePropNum < fieldTypeProperties.size();
          ++fieldTypePropNum) {
        String fieldTypePropName = fieldTypeProperties.getName(fieldTypePropNum);
        if (fieldTypePropName.equals(FieldType.ANALYZER)) {
          analyzerProperties =
              (SimpleOrderedMap) fieldTypeProperties.getVal(fieldTypePropNum);
        } else if (fieldTypePropName.equals(FieldType.INDEX_ANALYZER)) {
          indexAnalyzerProperties =
              (SimpleOrderedMap) fieldTypeProperties.getVal(fieldTypePropNum);
        } else if (fieldTypePropName.equals(FieldType.QUERY_ANALYZER)) {
          queryAnalyzerProperties =
              (SimpleOrderedMap) fieldTypeProperties.getVal(fieldTypePropNum);
        } else if (fieldTypePropName.equals(FieldType.MULTI_TERM_ANALYZER)) {
          multiTermAnalyzerProperties =
              (SimpleOrderedMap) fieldTypeProperties.getVal(fieldTypePropNum);
        } else if (fieldTypePropName.equals(FieldType.SIMILARITY)) {
          perFieldSimilarityProperties =
              (SimpleOrderedMap) fieldTypeProperties.getVal(fieldTypePropNum);
        } else {
          writeAttr(fieldTypePropName, fieldTypeProperties.getVal(fieldTypePropNum).toString());
        }
      }
      boolean isEmptyTag =
          null == analyzerProperties
              && null == indexAnalyzerProperties
              && null == queryAnalyzerProperties
              && null == multiTermAnalyzerProperties
              && null == perFieldSimilarityProperties;
      if (isEmptyTag) {
        closeStartTag(true);
      } else {
        closeStartTag(false);
        incLevel();
        if (null != analyzerProperties) writeAnalyzer(analyzerProperties, null);
        if (null != indexAnalyzerProperties)
          writeAnalyzer(indexAnalyzerProperties, FieldType.INDEX);
        if (null != queryAnalyzerProperties)
          writeAnalyzer(queryAnalyzerProperties, FieldType.QUERY);
        if (null != multiTermAnalyzerProperties)
          writeAnalyzer(multiTermAnalyzerProperties, FieldType.MULTI_TERM);
        if (null != perFieldSimilarityProperties) writeSimilarity(perFieldSimilarityProperties);
        decLevel();
        endTag(IndexSchema.FIELD_TYPE);
      }
    }
  }

  private void writeSimilarity(SimpleOrderedMap similarityProperties) throws IOException {
    openStartTag(IndexSchema.SIMILARITY);
    writeAttr(
        SimilarityFactory.CLASS_NAME,
        similarityProperties.get(SimilarityFactory.CLASS_NAME).toString());
    if (similarityProperties.size() > 1) {
      closeStartTag(false);
      incLevel();
      writeNamedList(null, similarityProperties);
      decLevel();
      endTag(IndexSchema.SIMILARITY);
    } else {
      closeStartTag(true);
    }
  }

  @SuppressWarnings({"unchecked"})
  private void writeAnalyzer(SimpleOrderedMap analyzerProperties, String analyzerType)
      throws IOException {
    openStartTag(FieldType.ANALYZER);
    if (null != analyzerType) {
      writeAttr(FieldType.TYPE, analyzerType);
    }
    List> charFilterPropertiesList = null;
    SimpleOrderedMap tokenizerProperties = null;
    List> filterPropertiesList = null;
    for (int i = 0; i < analyzerProperties.size(); ++i) {
      String name = analyzerProperties.getName(i);
      if (name.equals(FieldType.CHAR_FILTERS)) {
        charFilterPropertiesList = (List>) analyzerProperties.getVal(i);
      } else if (name.equals(FieldType.TOKENIZER)) {
        tokenizerProperties = (SimpleOrderedMap) analyzerProperties.getVal(i);
      } else if (name.equals(FieldType.FILTERS)) {
        filterPropertiesList = (List>) analyzerProperties.getVal(i);
      } else if (name.equals(FieldType.CLASS_NAME)) {
        if (!"solr.TokenizerChain".equals(analyzerProperties.getVal(i))) {
          writeAttr(name, analyzerProperties.getVal(i).toString());
        }
      } else if (name.equals(IndexSchema.LUCENE_MATCH_VERSION_PARAM)) {
        writeAttr(name, analyzerProperties.getVal(i).toString());
      }
    }
    boolean isEmptyTag =
        null == charFilterPropertiesList
            && null == tokenizerProperties
            && null == filterPropertiesList;
    if (isEmptyTag) {
      closeStartTag(true);
    } else {
      closeStartTag(false);
      incLevel();
      if (null != charFilterPropertiesList) {
        for (SimpleOrderedMap charFilterProperties : charFilterPropertiesList) {
          openStartTag(FieldType.CHAR_FILTER);
          for (int i = 0; i < charFilterProperties.size(); ++i) {
            writeAttr(charFilterProperties.getName(i), charFilterProperties.getVal(i).toString());
          }
          closeStartTag(true);
        }
      }
      if (null != tokenizerProperties) {
        openStartTag(FieldType.TOKENIZER);
        for (int i = 0; i < tokenizerProperties.size(); ++i) {
          writeAttr(tokenizerProperties.getName(i), tokenizerProperties.getVal(i).toString());
        }
        closeStartTag(true);
      }
      if (null != filterPropertiesList) {
        for (SimpleOrderedMap filterProperties : filterPropertiesList) {
          openStartTag(FieldType.FILTER);
          for (int i = 0; i < filterProperties.size(); ++i) {
            writeAttr(filterProperties.getName(i), filterProperties.getVal(i).toString());
          }
          closeStartTag(true);
        }
      }
      decLevel();
      endTag(FieldType.ANALYZER);
    }
  }

  void openStartTag(String tag) throws IOException {
    if (doIndent) indent();
    writer.write('<');
    writer.write(tag);
  }

  void closeStartTag(boolean isEmptyTag) throws IOException {
    if (isEmptyTag) writer.write('/');
    writer.write('>');
  }

  void endTag(String tag) throws IOException {
    endTag(tag, true);
  }

  void endTag(String tag, boolean indentThisTag) throws IOException {
    if (doIndent && indentThisTag) indent();

    writer.write('<');
    writer.write('/');
    writer.write(tag);
    writer.write('>');
  }

  /** 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('"');
    }
  }

  @Override
  public void writeNamedList(String name, NamedList val) throws IOException {
    // name is ignored - this method is only used for SimilarityFactory
    int sz = val.size();
    for (int i = 0; i < sz; i++) {
      String valName = val.getName(i);
      if (!valName.equals(SimilarityFactory.CLASS_NAME)) {
        writeVal(valName, val.getVal(i));
      }
    }
  }

  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 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 : map.entrySet()) {
      Object k = entry.getKey();
      Object v = entry.getValue();
      // if (sz 0) {
        if (doIndent) indent();
        writer.write("");
      }
    }
  }

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

  @Override
  public void writeArray(String name, Iterator iter, boolean raw) throws IOException {
    if (iter.hasNext()) {
      startTag("arr", name, false);
      incLevel();
      while (iter.hasNext()) {
        writeVal(null, iter.next(), raw);
      }
      decLevel();
      if (doIndent) indent();
      writer.write("");
    } 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(")
  //
  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('>');
  }

  @Override
  public void writeStartDocumentList(
      String name, long start, int size, long numFound, Float maxScore, Boolean numFoundExact)
      throws IOException {
    // no-op
  }

  @Override
  public void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx)
      throws IOException {
    // no-op
  }

  @Override
  public void writeEndDocumentList() throws IOException {
    // no-op
  }
}