/*
* 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.update;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
/**
*
*/
public class AddUpdateCommand extends UpdateCommand implements Iterable<Document> {
// optional id in "internal" indexed form... if it is needed and not supplied,
// it will be obtained from the doc.
private BytesRef indexedId;
/**
* Higher level SolrInputDocument, normally used to construct the Lucene Document
* to index.
*/
public SolrInputDocument solrDoc;
/**
* This is the version of a document, previously indexed, on which the current
* update depends on. This version could be that of a previous in-place update
* or a full update. A negative value here, e.g. -1, indicates that this add
* update does not depend on a previous update.
*/
public long prevVersion = -1;
public boolean overwrite = true;
public Term updateTerm;
public int commitWithin = -1;
public boolean isLastDocInBatch = false;
public AddUpdateCommand(SolrQueryRequest req) {
super(req);
}
@Override
public String name() {
return "add";
}
/** Reset state to reuse this object with a different document in the same request */
public void clear() {
solrDoc = null;
indexedId = null;
updateTerm = null;
isLastDocInBatch = false;
version = 0;
}
public SolrInputDocument getSolrInputDocument() {
return solrDoc;
}
/** Creates and returns a lucene Document to index. Any changes made to the returned Document
* will not be reflected in the SolrInputDocument, or future calls to this method. This defaults
* to false for the inPlaceUpdate parameter of {@link #getLuceneDocument(boolean)}.
*/
public Document getLuceneDocument() {
return getLuceneDocument(false);
}
/** Creates and returns a lucene Document to index. Any changes made to the returned Document
* will not be reflected in the SolrInputDocument, or future calls to this method.
* @param inPlaceUpdate Whether this document will be used for in-place updates.
*/
public Document getLuceneDocument(boolean inPlaceUpdate) {
return DocumentBuilder.toDocument(getSolrInputDocument(), req.getSchema(), inPlaceUpdate);
}
/** Returns the indexed ID for this document. The returned BytesRef is retained across multiple calls, and should not be modified. */
public BytesRef getIndexedId() {
if (indexedId == null) {
IndexSchema schema = req.getSchema();
SchemaField sf = schema.getUniqueKeyField();
if (sf != null) {
if (solrDoc != null) {
SolrInputField field = solrDoc.getField(sf.getName());
int count = field==null ? 0 : field.getValueCount();
if (count == 0) {
if (overwrite) {
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,"Document is missing mandatory uniqueKey field: " + sf.getName());
}
} else if (count > 1) {
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,"Document contains multiple values for uniqueKey field: " + field);
} else {
BytesRefBuilder b = new BytesRefBuilder();
sf.getType().readableToIndexed(field.getFirstValue().toString(), b);
indexedId = b.get();
}
}
}
}
return indexedId;
}
public void setIndexedId(BytesRef indexedId) {
this.indexedId = indexedId;
}
public String getPrintableId() {
if (req != null) {
IndexSchema schema = req.getSchema();
SchemaField sf = schema.getUniqueKeyField();
if (solrDoc != null && sf != null) {
SolrInputField field = solrDoc.getField(sf.getName());
if (field != null) {
return field.getFirstValue().toString();
}
}
}
return "(null)";
}
/**
* @return String id to hash
*/
public String getHashableId() {
String id = null;
IndexSchema schema = req.getSchema();
SchemaField sf = schema.getUniqueKeyField();
if (sf != null) {
if (solrDoc != null) {
SolrInputField field = solrDoc.getField(sf.getName());
int count = field == null ? 0 : field.getValueCount();
if (count == 0) {
if (overwrite) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Document is missing mandatory uniqueKey field: "
+ sf.getName());
}
} else if (count > 1) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Document contains multiple values for uniqueKey field: " + field);
} else {
return field.getFirstValue().toString();
}
}
}
return id;
}
public boolean isBlock() {
return solrDoc.hasChildDocuments();
}
@Override
public Iterator<Document> iterator() {
return new Iterator<Document>() {
Iterator<SolrInputDocument> iter;
{
List<SolrInputDocument> all = flatten(solrDoc);
String idField = getHashableId();
boolean isVersion = version != 0;
for (SolrInputDocument sdoc : all) {
sdoc.setField("_root_", idField); // should this be a string or the same type as the ID?
if(isVersion) sdoc.setField(CommonParams.VERSION_FIELD, version);
// TODO: if possible concurrent modification exception (if SolrInputDocument not cloned and is being forwarded to replicas)
// then we could add this field to the generated lucene document instead.
}
iter = all.iterator();
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public Document next() {
return DocumentBuilder.toDocument(iter.next(), req.getSchema());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private List<SolrInputDocument> flatten(SolrInputDocument root) {
List<SolrInputDocument> unwrappedDocs = new ArrayList<>();
recUnwrapp(unwrappedDocs, root);
return unwrappedDocs;
}
private void recUnwrapp(List<SolrInputDocument> unwrappedDocs, SolrInputDocument currentDoc) {
List<SolrInputDocument> children = currentDoc.getChildDocuments();
if (children != null) {
for (SolrInputDocument child : children) {
recUnwrapp(unwrappedDocs, child);
}
}
unwrappedDocs.add(currentDoc);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append(",id=").append(getPrintableId());
if (!overwrite) sb.append(",overwrite=").append(overwrite);
if (commitWithin != -1) sb.append(",commitWithin=").append(commitWithin);
sb.append('}');
return sb.toString();
}
/**
* Is this add update an in-place update? An in-place update is one where only docValues are
* updated, and a new docment is not indexed.
*/
public boolean isInPlaceUpdate() {
return (prevVersion >= 0);
}
}