package org.apache.solr.search.federated;
/*
* 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.
*/
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.lucene.index.IndexableField;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.handler.component.ShardRequest;
import org.apache.solr.schema.CopyField;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.ReturnFields;
import org.apache.solr.search.federated.DuplicateDocumentList;
import org.apache.solr.search.federated.FilterDJoinQParserSearchComponent;
/**
* Inspect the result documents for merge parents, and merge the children.
*/
public class MergeSearchComponent extends FilterDJoinQParserSearchComponent {
// return whether to do a merge at all
private boolean doMerge(ResponseBuilder rb) {
SolrParams params = rb.req.getParams();
if (! params.getBool(getName(), false)) {
return false;
}
if (rb.stage != ResponseBuilder.STAGE_GET_FIELDS) {
return false;
}
return true;
}
// need to ask distributed servers for source fields for all copy fields needed in the aggregator
// also add in [shard] field if we want shard info
@Override
public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) {
// do the filterQParser stuff first
super.modifyRequest(rb, who, sreq);
if (! doMerge(rb)) {
return;
}
ReturnFields rf = rb.rsp.getReturnFields();
if (rf.wantsAllFields()) {
// we already have what we need since we ask for everything...
return;
}
IndexSchema schema = rb.req.getCore().getLatestSchema();
for (SchemaField field : schema.getFields().values()) {
if (! rf.wantsField(field.getName())) {
continue;
}
for (String source : schema.getCopySources(field.getName())) {
if (rf.wantsField(source)) {
continue;
}
sreq.params.add(CommonParams.FL, source);
}
}
}
@Override
public void finishStage(ResponseBuilder rb) {
if (! doMerge(rb)) {
return;
}
try {
mergeAndConvert(rb);
} catch (RuntimeException e) {
// remove response docs, leaving the error stack trace
rb.rsp.getValues().remove("response");
throw e;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void mergeAndConvert(ResponseBuilder rb) {
IndexSchema schema = rb.req.getCore().getLatestSchema();
ReturnFields rf = rb.rsp.getReturnFields();
SolrDocumentList docs = (SolrDocumentList)rb.rsp.getValues().get("response");
for (SolrDocument parent : docs) {
parent.remove(DuplicateDocumentList.MERGE_PARENT_FIELD);
Set shardList = new HashSet();
Float score = null;
for (SolrDocument doc : parent.getChildDocuments()) {
String shard = (String)doc.getFieldValue("[shard]");
NamedList nl = null;
if (shard != null) {
nl = new NamedList();
nl.add("address", shard);
shardList.add(nl);
}
for (String fieldName : doc.getFieldNames()) {
Object value = doc.getFieldValue(fieldName);
for (CopyField cf : schema.getCopyFieldsList(fieldName)) {
SchemaField field = cf.getDestination();
addConvertedFieldValue(shard, parent, value, field);
}
SchemaField field = schema.getFieldOrNull(fieldName);
if (fieldName.equals("score")) {
score = Math.max(score != null ? score : 0.0f, (Float)value);
if (nl != null) {
nl.add("score", score);
}
} else if (field != null) {
addConvertedFieldValue(shard, parent, value, field);
}
}
}
if (shardList.size() > 0) {
parent.setField("[shard]", shardList);
} else {
parent.removeFields("[shard]");
}
if (score != null) {
parent.setField("score", score);
} else {
parent.removeFields("score");
}
// check required fields are present, and then remove if non-stored
for (SchemaField field : schema.getFields().values()) {
Object value = parent.getFieldValue(field.getName());
if (value == null) {
value = field.getDefaultValue();
if (value != null) {
parent.setField(field.getName(), value);
}
}
if (value == null && field.isRequired() && rf.wantsField(field.getName())) {
throw new MergeException.MissingRequiredField(field);
}
if (! field.stored()) {
parent.removeFields(field.getName());
}
}
// remove child documents
while (parent.getChildDocumentCount() > 0) {
parent.getChildDocuments().remove(0);
}
}
}
private void convert(SchemaField field, Object value, Set<Object> valueSet) {
IndexableField indexable = field.getType().createField(field, value, 1.0f);
valueSet.add(field.getType().toObject(indexable));
}
@SuppressWarnings({ "unchecked", "rawtypes", "serial" })
private void addConvertedFieldValue(String shard, SolrDocument superDoc, Object shardValue, SchemaField field) {
Object mergeValue = superDoc.getFieldValue(field.getName());
if (field.getType() instanceof MergeAbstractFieldType) {
Object newValue = ((MergeAbstractFieldType)field.getType()).merge(shard, mergeValue, shardValue);
if (newValue != MergeAbstractFieldType.DEFAULT_MERGE_BEHAVIOUR) {
superDoc.setField(field.getName(), mergeValue);
return;
}
}
// continue with the default merge behaviour...
if (shardValue == null) {
return;
}
Set newValues = new HashSet() {
@Override
public boolean add(Object value) {
if (value == null) return false;
return super.add(value);
}
};
if (shardValue instanceof List) {
for (Object value : (List)shardValue) {
convert(field, value, newValues);
}
} else {
convert(field, shardValue, newValues);
}
if (newValues.size() == 0) {
return;
}
if (field.multiValued()) {
Set set = (Set)mergeValue;
if (set == null) {
set = new HashSet();
superDoc.setField(field.getName(), set);
}
set.addAll(newValues);
} else {
newValues.add(mergeValue);
if (newValues.size() > 1) {
throw new MergeException.FieldNotMultiValued(field);
} else if (newValues.size() == 1) {
superDoc.setField(field.getName(), newValues.iterator().next());
}
}
}
}