/** * 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.Writer; import java.io.IOException; import java.util.*; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import org.apache.lucene.util.UnicodeUtil; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.uninverted.UnInvertedField; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.DocIterator; import org.apache.solr.search.DocList; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; /** * A description of the PHP serialization format can be found here: * http://www.hurring.com/scott/code/perl/serialize/ */ public class PHPSerializedResponseWriter implements QueryResponseWriter { static String CONTENT_TYPE_PHP_UTF8="text/x-php-serialized;charset=UTF-8"; public void init(NamedList n) { } public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { PHPSerializedWriter w = new PHPSerializedWriter(writer, req, rsp); try { w.writeResponse(); } finally { w.close(); } } public String getContentType(SolrQueryRequest request, SolrQueryResponse response) { return CONTENT_TYPE_TEXT_UTF8; } } class PHPSerializedWriter extends JSONWriter { final UnicodeUtil.UTF8Result utf8; public PHPSerializedWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) { super(writer, req, rsp); this.utf8 = new UnicodeUtil.UTF8Result(); // never indent serialized PHP data doIndent = false; } @Override public void writeResponse() throws IOException { Boolean omitHeader = req.getParams().getBool(CommonParams.OMIT_HEADER); if(omitHeader != null && omitHeader) rsp.getValues().remove("responseHeader"); writeNamedList(null, rsp.getValues()); } @Override public void writeNamedList(String name, NamedList val) throws IOException { writeNamedListAsMapMangled(name,val); } @Override public void writeDoc(String name, Collection<Fieldable> fields, Set<String> returnFields, Map pseudoFields) throws IOException { ArrayList<Fieldable> single = new ArrayList<Fieldable>(); LinkedHashMap<String, MultiValueField> multi = new LinkedHashMap<String, MultiValueField>(); for (Fieldable ff : fields) { String fname = ff.name(); if (returnFields!=null && !returnFields.contains(fname)) { continue; } // if the field is multivalued, it may have other values further on... so // build up a list for each multi-valued field. SchemaField sf = schema.getField(fname); if (sf.multiValued()) { MultiValueField mf = multi.get(fname); if (mf==null) { mf = new MultiValueField(sf, ff); multi.put(fname, mf); } else { mf.fields.add(ff); } } else { single.add(ff); } } // obtain number of fields in doc writeArrayOpener(single.size() + multi.size() + ((pseudoFields!=null) ? pseudoFields.size() : 0)); // output single value fields for(Fieldable ff : single) { SchemaField sf = schema.getField(ff.name()); writeKey(ff.name(),true); sf.write(this, ff.name(), ff); } // output multi value fields for(MultiValueField mvf : multi.values()) { writeKey(mvf.sfield.getName(), true); writeArrayOpener(mvf.fields.size()); int i = 0; for (Fieldable ff : mvf.fields) { writeKey(i++, false); mvf.sfield.write(this, null, ff); } writeArrayCloser(); } // output pseudo fields if (pseudoFields !=null && pseudoFields.size()>0) { writeMap(null,pseudoFields,true,false); } writeArrayCloser(); } @Override public void writeDocList(String name, DocList ids, Set<String> fields, Map otherFields) 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=ids.size(); writeMapOpener(includeScore ? 4 : 3); writeKey("numFound",false); writeInt(null,ids.matches()); writeKey("start",false); writeInt(null,ids.offset()); if (includeScore) { writeKey("maxScore",false); writeFloat(null,ids.maxScore()); } writeKey("docs",false); writeArrayOpener(sz); SolrIndexSearcher searcher = req.getSearcher(); DocIterator iterator = ids.iterator(); for (int i=0; i<sz; i++) { int id = iterator.nextDoc(); Document doc = searcher.doc(id, fields); writeKey(i, false); writeDoc(null, doc, fields, (includeScore ? iterator.score() : 0.0f), includeScore); } writeMapCloser(); if (otherFields !=null) { writeMap(null, otherFields, true, false); } writeMapCloser(); } @Override public void writeSolrDocument(String name, SolrDocument doc, Set<String> returnFields, Map pseudoFields) throws IOException { LinkedHashMap <String,Object> single = new LinkedHashMap<String, Object>(); LinkedHashMap <String,Object> multi = new LinkedHashMap<String, Object>(); int pseudoSize = pseudoFields != null ? pseudoFields.size() : 0; for (String fname : doc.getFieldNames()) { if(returnFields != null && !returnFields.contains(fname)){ continue; } Object val = doc.getFieldValue(fname); SchemaField sf = schema.getFieldOrNull(fname); if (sf != null && sf.multiValued()) { multi.put(fname, val); }else{ single.put(fname, val); } } writeMapOpener(single.size() + multi.size() + pseudoSize); for(String fname: single.keySet()){ Object val = single.get(fname); writeKey(fname, true); writeVal(fname, val); } for(String fname: multi.keySet()){ writeKey(fname, true); Object val = multi.get(fname); if (!(val instanceof Collection)) { // should never be reached if multivalued fields are stored as a Collection // so I'm assuming a size of 1 just to wrap the single value writeArrayOpener(1); writeVal(fname, val); writeArrayCloser(); }else{ writeVal(fname, val); } } if (pseudoSize > 0) { writeMap(null,pseudoFields,true, false); } writeMapCloser(); } @Override public void writeSolrDocumentList(String name, SolrDocumentList docs, Set<String> fields, Map otherFields) 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.size(); writeMapOpener(includeScore ? 4 : 3); writeKey("numFound",false); writeLong(null,docs.getNumFound()); writeKey("start",false); writeLong(null,docs.getStart()); if (includeScore && docs.getMaxScore() != null) { writeKey("maxScore",false); writeFloat(null,docs.getMaxScore()); } writeKey("sum",false); writeDouble(null,docs.getSum()); writeKey("max",false); writeDouble(null,docs.getMax()); writeKey("min",false); writeDouble(null,docs.getMin()); writeKey("docs",false); writeArrayOpener(sz); for (int i=0; i<sz; i++) { writeKey(i, false); writeSolrDocument(null, docs.get(i), fields, otherFields); } writeArrayCloser(); if (otherFields !=null) { writeMap(null, otherFields, true, false); } writeMapCloser(); } @Override public void writeArray(String name, Object[] val) throws IOException { writeMapOpener(val.length); for(int i=0; i < val.length; i++) { writeKey(i, false); writeVal(String.valueOf(i), val[i]); } writeMapCloser(); } @Override public void writeArray(String name, Iterator val) throws IOException { ArrayList vals = new ArrayList(); while( val.hasNext() ) { vals.add(val.next()); } writeArray(name, vals.toArray()); } @Override public void writeMapOpener(int size) throws IOException, IllegalArgumentException { // negative size value indicates that something has gone wrong if (size < 0) { throw new IllegalArgumentException("Map size must not be negative"); } writer.write("a:"+size+":{"); } @Override public void writeMapSeparator() throws IOException { /* NOOP */ } @Override public void writeMapCloser() throws IOException { writer.write('}'); } @Override public void writeArrayOpener(int size) throws IOException, IllegalArgumentException { // negative size value indicates that something has gone wrong if (size < 0) { throw new IllegalArgumentException("Array size must not be negative"); } writer.write("a:"+size+":{"); } @Override public void writeArraySeparator() throws IOException { /* NOOP */ } @Override public void writeArrayCloser() throws IOException { writer.write('}'); } @Override public void writeNull(String name) throws IOException { writer.write("N;"); } @Override protected void writeKey(String fname, boolean needsEscaping) throws IOException { writeStr(null, fname, needsEscaping); } void writeKey(int val, boolean needsEscaping) throws IOException { writeInt(null, String.valueOf(val)); } @Override public void writeBool(String name, boolean val) throws IOException { writer.write(val ? "b:1;" : "b:0;"); } @Override public void writeBool(String name, String val) throws IOException { writeBool(name, val.charAt(0) == 't'); } @Override public void writeInt(String name, String val) throws IOException { writer.write("i:"+val+";"); } @Override public void writeLong(String name, String val) throws IOException { writeInt(name,val); } @Override public void writeFloat(String name, String val) throws IOException { writeDouble(name,val); } @Override public void writeDouble(String name, String val) throws IOException { writer.write("d:"+val+";"); } @Override public void writeStr(String name, String val, boolean needsEscaping) throws IOException { // serialized PHP strings don't need to be escaped at all, however the // string size reported needs be the number of bytes rather than chars. UnicodeUtil.UTF16toUTF8(val, 0, val.length(), utf8); int nBytes = utf8.length; writer.write("s:"); writer.write(Integer.toString(nBytes)); writer.write(":\""); writer.write(val); writer.write("\";"); } }