/* * 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.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; 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; import static org.apache.solr.common.params.CommonParams.NAME; /** * @lucene.internal */ public class SchemaXmlWriter extends TextResponseWriter { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final char[] XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".toCharArray(); private static final char[] MANAGED_SCHEMA_DO_NOT_EDIT_WARNING = "<!-- Solr managed schema - automatically generated - DO NOT EDIT -->".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); } public void writeResponse() throws IOException { writer.write(XML_DECLARATION); if (emitManagedSchemaDoNotEditWarning) { if (doIndent) { writer.write('\n'); } writer.write(MANAGED_SCHEMA_DO_NOT_EDIT_WARNING); } @SuppressWarnings("unchecked") Map<String,Object> schemaProperties = (Map<String , Object>)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<String, Object> 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<Object>) val); } else if (schemaPropName.equals(IndexSchema.FIELD_TYPES)) { writeFieldTypes((List<SimpleOrderedMap<Object>>) val); } else if (schemaPropName.equals(IndexSchema.FIELDS)) { @SuppressWarnings("unchecked") List<SimpleOrderedMap<Object>> fieldPropertiesList = (List<SimpleOrderedMap<Object>>) val; for (SimpleOrderedMap<Object> 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)) { @SuppressWarnings("unchecked") List<SimpleOrderedMap<Object>> dynamicFieldPropertiesList = (List<SimpleOrderedMap<Object>>) val; for (SimpleOrderedMap<Object> 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)) { @SuppressWarnings("unchecked") List<SimpleOrderedMap<Object>> copyFieldPropertiesList = (List<SimpleOrderedMap<Object>>) val; for (SimpleOrderedMap<Object> 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); } private void writeFieldTypes(List<SimpleOrderedMap<Object>> fieldTypePropertiesList) throws IOException { for (SimpleOrderedMap<Object> fieldTypeProperties : fieldTypePropertiesList) { SimpleOrderedMap<Object> analyzerProperties = null; SimpleOrderedMap<Object> indexAnalyzerProperties = null; SimpleOrderedMap<Object> queryAnalyzerProperties = null; SimpleOrderedMap<Object> multiTermAnalyzerProperties = null; SimpleOrderedMap<Object> 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<Object>)fieldTypeProperties.getVal(fieldTypePropNum); } else if (fieldTypePropName.equals(FieldType.INDEX_ANALYZER)) { indexAnalyzerProperties = (SimpleOrderedMap<Object>)fieldTypeProperties.getVal(fieldTypePropNum); } else if (fieldTypePropName.equals(FieldType.QUERY_ANALYZER)) { queryAnalyzerProperties = (SimpleOrderedMap<Object>)fieldTypeProperties.getVal(fieldTypePropNum); } else if (fieldTypePropName.equals(FieldType.MULTI_TERM_ANALYZER)) { multiTermAnalyzerProperties = (SimpleOrderedMap<Object>)fieldTypeProperties.getVal(fieldTypePropNum); } else if (fieldTypePropName.equals(FieldType.SIMILARITY)) { perFieldSimilarityProperties = (SimpleOrderedMap<Object>)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<Object> 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); } } private void writeAnalyzer(SimpleOrderedMap<Object> analyzerProperties, String analyzerType) throws IOException { openStartTag(FieldType.ANALYZER); if (null != analyzerType) { writeAttr(FieldType.TYPE, analyzerType); } List<SimpleOrderedMap<Object>> charFilterPropertiesList = null; SimpleOrderedMap<Object> tokenizerProperties = null; List<SimpleOrderedMap<Object>> filterPropertiesList = null; for (int i = 0 ; i < analyzerProperties.size() ; ++i) { String name = analyzerProperties.getName(i); if (name.equals(FieldType.CHAR_FILTERS)) { charFilterPropertiesList = (List<SimpleOrderedMap<Object>>)analyzerProperties.getVal(i); } else if (name.equals(FieldType.TOKENIZER)) { tokenizerProperties = (SimpleOrderedMap<Object>)analyzerProperties.getVal(i); } else if (name.equals(FieldType.FILTERS)) { filterPropertiesList = (List<SimpleOrderedMap<Object>>)analyzerProperties.getVal(i); } else if (name.equals(FieldType.CLASS_NAME)) { if ( ! "solr.TokenizerChain".equals(analyzerProperties.getVal(i))) { 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<Object> 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<Object> 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 : (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('>'); } @Override public void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) 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 } }