blob: 155f267a53baf3e094f1967c7a570dd8b7df9ba5 [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.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.UnicodeUtil;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.ReturnFields;
/**
* 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";
private String contentType = CONTENT_TYPE_PHP_UTF8;
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList namedList) {
String contentType = (String) namedList.get("content-type");
if (contentType != null) {
this.contentType = contentType;
}
}
@Override
public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
PHPSerializedWriter w = new PHPSerializedWriter(writer, req, rsp);
try {
w.writeResponse();
} finally {
w.close();
}
}
@Override
public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
return contentType;
}
}
class PHPSerializedWriter extends JSONWriter {
byte[] utf8;
public PHPSerializedWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
super(writer, req, rsp);
this.utf8 = BytesRef.EMPTY_BYTES;
// 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.removeResponseHeader();
writeNamedList(null, rsp.getValues());
}
@Override
public void writeNamedList(String name, @SuppressWarnings({"rawtypes"})NamedList val) throws IOException {
writeNamedListAsMapMangled(name,val);
}
@Deprecated
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore) throws IOException
{
throw new UnsupportedOperationException();
}
@Override
public void writeStartDocumentList(String name,
long start, int size, long numFound, Float maxScore, Boolean numFoundExact) throws IOException
{
writeMapOpener(headerSize(maxScore, numFoundExact));
writeKey("numFound",false);
writeLong(null,numFound);
writeKey("start",false);
writeLong(null,start);
if (maxScore!=null) {
writeKey("maxScore",false);
writeFloat(null,maxScore);
}
if (numFoundExact != null) {
writeKey("numFoundExact",false);
writeBool(null, numFoundExact);
}
writeKey("docs",false);
writeArrayOpener(size);
}
@Override
public void writeEndDocumentList() throws IOException
{
writeArrayCloser(); // doc list
writeMapCloser();
}
@Override
public void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx) throws IOException
{
writeKey(idx, false);
LinkedHashMap <String,Object> single = new LinkedHashMap<>();
LinkedHashMap <String,Object> multi = new LinkedHashMap<>();
for (String fname : doc.getFieldNames()) {
if (returnFields != null && !returnFields.wantsField(fname)) {
continue;
}
Object val = doc.getFieldValue(fname);
if (val instanceof Collection) {
multi.put(fname, val);
}else{
single.put(fname, val);
}
}
writeMapOpener(single.size() + multi.size());
for(Map.Entry<String, Object> entry : single.entrySet()){
String fname = entry.getKey();
Object val = entry.getValue();
writeKey(fname, true);
writeVal(fname, val);
}
for(Map.Entry<String, Object> entry : multi.entrySet()){
String fname = entry.getKey();
writeKey(fname, true);
Object val = entry.getValue();
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);
}
}
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
@SuppressWarnings({"unchecked"})
public void writeArray(String name, @SuppressWarnings({"rawtypes"})Iterator val) throws IOException {
@SuppressWarnings({"rawtypes"})
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
public 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.
utf8 = ArrayUtil.grow(utf8, val.length() * UnicodeUtil.MAX_UTF8_BYTES_PER_CHAR);
final int nBytes = UnicodeUtil.UTF16toUTF8(val, 0, val.length(), utf8);
writer.write("s:");
writer.write(Integer.toString(nBytes));
writer.write(":\"");
writer.write(val);
writer.write("\";");
}
}