blob: a19fd2de9794492844be83bb92c4709cd8260a1e [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.client.solrj.request;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.LBHttpSolrClient;
import org.apache.solr.client.solrj.impl.LBSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.DocRouter;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.UpdateParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.XML;
import static org.apache.solr.common.params.ShardParams._ROUTE_;
/**
*
*
* @since solr 1.3
*/
public class UpdateRequest extends AbstractUpdateRequest {
public static final String REPFACT = "rf";
/**
* @deprecated Solr now always includes in the response the {@link #REPFACT}, this parameter
* doesn't need to be explicitly set
*/
@Deprecated // SOLR-14034
public static final String MIN_REPFACT = "min_rf";
public static final String VER = "ver";
public static final String OVERWRITE = "ow";
public static final String COMMIT_WITHIN = "cw";
private Map<SolrInputDocument,Map<String,Object>> documents = null;
private Iterator<SolrInputDocument> docIterator = null;
private Map<String,Map<String,Object>> deleteById = null;
private List<String> deleteQuery = null;
private boolean isLastDocInBatch = false;
public UpdateRequest() {
super(METHOD.POST, "/update");
}
public UpdateRequest(String url) {
super(METHOD.POST, url);
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* clear the pending documents and delete commands
*/
public void clear() {
if (documents != null) {
documents.clear();
}
if (deleteById != null) {
deleteById.clear();
}
if (deleteQuery != null) {
deleteQuery.clear();
}
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* Add a SolrInputDocument to this request
*
* @throws NullPointerException if the document is null
*/
public UpdateRequest add(final SolrInputDocument doc) {
Objects.requireNonNull(doc, "Cannot add a null SolrInputDocument");
if (documents == null) {
documents = new LinkedHashMap<>();
}
documents.put(doc, null);
return this;
}
public UpdateRequest add(String... fields) {
return add(new SolrInputDocument(fields));
}
/**
* Add a SolrInputDocument to this request
* @param doc the document
* @param overwrite true if the document should overwrite existing docs with the same id
* @throws NullPointerException if the document is null
*/
public UpdateRequest add(final SolrInputDocument doc, Boolean overwrite) {
return add(doc, null, overwrite);
}
/**
* Add a SolrInputDocument to this request
* @param doc the document
* @param commitWithin the time horizon by which the document should be committed (in ms)
* @throws NullPointerException if the document is null
*/
public UpdateRequest add(final SolrInputDocument doc, Integer commitWithin) {
return add(doc, commitWithin, null);
}
/**
* Add a SolrInputDocument to this request
* @param doc the document
* @param commitWithin the time horizon by which the document should be committed (in ms)
* @param overwrite true if the document should overwrite existing docs with the same id
* @throws NullPointerException if the document is null
*/
public UpdateRequest add(final SolrInputDocument doc, Integer commitWithin, Boolean overwrite) {
Objects.requireNonNull(doc, "Cannot add a null SolrInputDocument");
if (documents == null) {
documents = new LinkedHashMap<>();
}
Map<String,Object> params = new HashMap<>(2);
if (commitWithin != null) params.put(COMMIT_WITHIN, commitWithin);
if (overwrite != null) params.put(OVERWRITE, overwrite);
documents.put(doc, params);
return this;
}
/**
* Add a collection of SolrInputDocuments to this request
*
* @throws NullPointerException if any of the documents in the collection are null
*/
public UpdateRequest add(final Collection<SolrInputDocument> docs) {
if (documents == null) {
documents = new LinkedHashMap<>();
}
for (SolrInputDocument doc : docs) {
Objects.requireNonNull(doc, "Cannot add a null SolrInputDocument");
documents.put(doc, null);
}
return this;
}
public UpdateRequest deleteById(String id) {
if (deleteById == null) {
deleteById = new LinkedHashMap<>();
}
deleteById.put(id, null);
return this;
}
public UpdateRequest deleteById(String id, String route) {
return deleteById(id, route, null);
}
public UpdateRequest deleteById(String id, String route, Long version) {
if (deleteById == null) {
deleteById = new LinkedHashMap<>();
}
Map<String, Object> params = (route == null && version == null) ? null : new HashMap<>(1);
if (version != null)
params.put(VER, version);
if (route != null)
params.put(_ROUTE_, route);
deleteById.put(id, params);
return this;
}
public UpdateRequest deleteById(List<String> ids) {
if (deleteById == null) {
deleteById = new LinkedHashMap<>();
}
for (String id : ids) {
deleteById.put(id, null);
}
return this;
}
public UpdateRequest deleteById(String id, Long version) {
return deleteById(id, null, version);
}
public UpdateRequest deleteByQuery(String q) {
if (deleteQuery == null) {
deleteQuery = new ArrayList<>();
}
deleteQuery.add(q);
return this;
}
public UpdateRequest withRoute(String route) {
if (params == null)
params = new ModifiableSolrParams();
params.set(_ROUTE_, route);
return this;
}
public UpdateResponse commit(SolrClient client, String collection) throws IOException, SolrServerException {
if (params == null)
params = new ModifiableSolrParams();
params.set(UpdateParams.COMMIT, "true");
return process(client, collection);
}
private interface ReqSupplier<T extends LBSolrClient.Req> {
T get(@SuppressWarnings({"rawtypes"})SolrRequest solrRequest, List<String> servers);
}
private <T extends LBSolrClient.Req> Map<String, T> getRoutes(DocRouter router,
DocCollection col, Map<String,List<String>> urlMap,
ModifiableSolrParams params, String idField,
ReqSupplier<T> reqSupplier) {
if ((documents == null || documents.size() == 0)
&& (deleteById == null || deleteById.size() == 0)) {
return null;
}
Map<String,T> routes = new HashMap<>();
if (documents != null) {
Set<Entry<SolrInputDocument,Map<String,Object>>> entries = documents.entrySet();
for (Entry<SolrInputDocument,Map<String,Object>> entry : entries) {
SolrInputDocument doc = entry.getKey();
Object id = doc.getFieldValue(idField);
if (id == null) {
return null;
}
Slice slice = router.getTargetSlice(id
.toString(), doc, null, null, col);
if (slice == null) {
return null;
}
List<String> urls = urlMap.get(slice.getName());
if (urls == null) {
return null;
}
String leaderUrl = urls.get(0);
T request = routes
.get(leaderUrl);
if (request == null) {
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.setMethod(getMethod());
updateRequest.setCommitWithin(getCommitWithin());
updateRequest.setParams(params);
updateRequest.setPath(getPath());
updateRequest.setBasicAuthCredentials(getBasicAuthUser(), getBasicAuthPassword());
updateRequest.setResponseParser(getResponseParser());
request = reqSupplier.get(updateRequest, urls);
routes.put(leaderUrl, request);
}
UpdateRequest urequest = (UpdateRequest) request.getRequest();
Map<String,Object> value = entry.getValue();
Boolean ow = null;
if (value != null) {
ow = (Boolean) value.get(OVERWRITE);
}
if (ow != null) {
urequest.add(doc, ow);
} else {
urequest.add(doc);
}
}
}
// Route the deleteById's
if (deleteById != null) {
Iterator<Map.Entry<String,Map<String,Object>>> entries = deleteById.entrySet()
.iterator();
while (entries.hasNext()) {
Map.Entry<String,Map<String,Object>> entry = entries.next();
String deleteId = entry.getKey();
Map<String,Object> map = entry.getValue();
Long version = null;
String route = null;
if (map != null) {
version = (Long) map.get(VER);
route = (String) map.get(_ROUTE_);
}
Slice slice = router.getTargetSlice(deleteId, null, route, null, col);
if (slice == null) {
return null;
}
List<String> urls = urlMap.get(slice.getName());
if (urls == null) {
return null;
}
String leaderUrl = urls.get(0);
T request = routes.get(leaderUrl);
if (request != null) {
UpdateRequest urequest = (UpdateRequest) request.getRequest();
urequest.deleteById(deleteId, route, version);
} else {
UpdateRequest urequest = new UpdateRequest();
urequest.setParams(params);
urequest.deleteById(deleteId, route, version);
urequest.setCommitWithin(getCommitWithin());
urequest.setBasicAuthCredentials(getBasicAuthUser(), getBasicAuthPassword());
request = reqSupplier.get(urequest, urls);
routes.put(leaderUrl, request);
}
}
}
return routes;
}
/**
* @param router to route updates with
* @param col DocCollection for the updates
* @param urlMap of the cluster
* @param params params to use
* @param idField the id field
* @return a Map of urls to requests
*/
public Map<String, LBSolrClient.Req> getRoutesToCollection(DocRouter router,
DocCollection col, Map<String,List<String>> urlMap,
ModifiableSolrParams params, String idField) {
return getRoutes(router, col, urlMap, params, idField, LBSolrClient.Req::new);
}
/**
* @param router to route updates with
* @param col DocCollection for the updates
* @param urlMap of the cluster
* @param params params to use
* @param idField the id field
* @return a Map of urls to requests
* @deprecated since 8.0, uses {@link #getRoutesToCollection(DocRouter, DocCollection, Map, ModifiableSolrParams, String)} instead
*/
@Deprecated
public Map<String,LBHttpSolrClient.Req> getRoutes(DocRouter router,
DocCollection col, Map<String,List<String>> urlMap,
ModifiableSolrParams params, String idField) {
return getRoutes(router, col, urlMap, params, idField, LBHttpSolrClient.Req::new);
}
public void setDocIterator(Iterator<SolrInputDocument> docIterator) {
this.docIterator = docIterator;
}
public void setDeleteQuery(List<String> deleteQuery) {
this.deleteQuery = deleteQuery;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
@Override
public Collection<ContentStream> getContentStreams() throws IOException {
return ClientUtils.toContentStreams(getXML(), ClientUtils.TEXT_XML);
}
public String getXML() throws IOException {
StringWriter writer = new StringWriter();
writeXML(writer);
writer.flush();
// If action is COMMIT or OPTIMIZE, it is sent with params
String xml = writer.toString();
// System.out.println( "SEND:"+xml );
return (xml.length() > 0) ? xml : null;
}
private List<Map<SolrInputDocument,Map<String,Object>>> getDocLists(Map<SolrInputDocument,Map<String,Object>> documents) {
List<Map<SolrInputDocument,Map<String,Object>>> docLists = new ArrayList<>();
Map<SolrInputDocument,Map<String,Object>> docList = null;
if (this.documents != null) {
Boolean lastOverwrite = true;
Integer lastCommitWithin = -1;
Set<Entry<SolrInputDocument,Map<String,Object>>> entries = this.documents
.entrySet();
for (Entry<SolrInputDocument,Map<String,Object>> entry : entries) {
Map<String,Object> map = entry.getValue();
Boolean overwrite = null;
Integer commitWithin = null;
if (map != null) {
overwrite = (Boolean) entry.getValue().get(OVERWRITE);
commitWithin = (Integer) entry.getValue().get(COMMIT_WITHIN);
}
if (!Objects.equals(overwrite, lastOverwrite)
|| !Objects.equals(commitWithin, lastCommitWithin)
|| docLists.isEmpty()) {
docList = new LinkedHashMap<>();
docLists.add(docList);
}
docList.put(entry.getKey(), entry.getValue());
lastCommitWithin = commitWithin;
lastOverwrite = overwrite;
}
}
if (docIterator != null) {
docList = new LinkedHashMap<>();
docLists.add(docList);
while (docIterator.hasNext()) {
SolrInputDocument doc = docIterator.next();
if (doc != null) {
docList.put(doc, null);
}
}
}
return docLists;
}
/**
* @since solr 1.4
*/
public UpdateRequest writeXML(Writer writer) throws IOException {
List<Map<SolrInputDocument,Map<String,Object>>> getDocLists = getDocLists(documents);
for (Map<SolrInputDocument,Map<String,Object>> docs : getDocLists) {
if ((docs != null && docs.size() > 0)) {
Entry<SolrInputDocument,Map<String,Object>> firstDoc = docs.entrySet()
.iterator().next();
Map<String,Object> map = firstDoc.getValue();
Integer cw = null;
Boolean ow = null;
if (map != null) {
cw = (Integer) firstDoc.getValue().get(COMMIT_WITHIN);
ow = (Boolean) firstDoc.getValue().get(OVERWRITE);
}
if (ow == null) ow = true;
int commitWithin = (cw != null && cw != -1) ? cw : this.commitWithin;
boolean overwrite = ow;
if (commitWithin > -1 || overwrite != true) {
writer.write("<add commitWithin=\"" + commitWithin + "\" "
+ "overwrite=\"" + overwrite + "\">");
} else {
writer.write("<add>");
}
Set<Entry<SolrInputDocument,Map<String,Object>>> entries = docs
.entrySet();
for (Entry<SolrInputDocument,Map<String,Object>> entry : entries) {
ClientUtils.writeXML(entry.getKey(), writer);
}
writer.write("</add>");
}
}
// Add the delete commands
boolean deleteI = deleteById != null && deleteById.size() > 0;
boolean deleteQ = deleteQuery != null && deleteQuery.size() > 0;
if (deleteI || deleteQ) {
if (commitWithin > 0) {
writer.append("<delete commitWithin=\"").append(String.valueOf(commitWithin)).append("\">");
} else {
writer.append("<delete>");
}
if (deleteI) {
for (Map.Entry<String,Map<String,Object>> entry : deleteById.entrySet()) {
writer.append("<id");
Map<String,Object> map = entry.getValue();
if (map != null) {
Long version = (Long) map.get(VER);
String route = (String)map.get(_ROUTE_);
if (version != null) {
writer.append(" version=\"").append(String.valueOf(version)).append('"');
}
if (route != null) {
writer.append(" _route_=\"").append(route).append('"');
}
}
writer.append(">");
XML.escapeCharData(entry.getKey(), writer);
writer.append("</id>");
}
}
if (deleteQ) {
for (String q : deleteQuery) {
writer.append("<query>");
XML.escapeCharData(q, writer);
writer.append("</query>");
}
}
writer.append("</delete>");
}
return this;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
public List<SolrInputDocument> getDocuments() {
if (documents == null) return null;
List<SolrInputDocument> docs = new ArrayList<>(documents.size());
docs.addAll(documents.keySet());
return docs;
}
public Map<SolrInputDocument,Map<String,Object>> getDocumentsMap() {
return documents;
}
public Iterator<SolrInputDocument> getDocIterator() {
return docIterator;
}
public List<String> getDeleteById() {
if (deleteById == null) return null;
List<String> deletes = new ArrayList<>(deleteById.keySet());
return deletes;
}
public Map<String,Map<String,Object>> getDeleteByIdMap() {
return deleteById;
}
public List<String> getDeleteQuery() {
return deleteQuery;
}
public boolean isLastDocInBatch() {
return isLastDocInBatch;
}
public void lastDocInBatch() {
isLastDocInBatch = true;
}
}