blob: 0ea30fb11386b8e098b5682f5ad2145bfe4c8b54 [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.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);
}
@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<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)) {
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)) {
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)) {
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);
}
@SuppressWarnings({"unchecked"})
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);
}
}
@SuppressWarnings({"unchecked"})
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());
}
} 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<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, @SuppressWarnings({"rawtypes"})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
@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 );
}
}
//
// 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 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
}
}