package io.searchbox.core;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.searchbox.action.AbstractAction;
import io.searchbox.action.BulkableAction;
import io.searchbox.action.GenericResultAbstractAction;
import io.searchbox.params.Parameters;
import io.searchbox.strings.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* The bulk API makes it possible to perform many index/delete operations in a
* single API call. This can greatly increase the indexing speed.
* <br/>
* <br/>
* Make sure that your source data (provided in Action instances) <b> does NOT
* have unescaped line-breaks</b> (e.g.: <code>"\n"</code> or <code>"\r\n"</code>)
* as doing so will break up the elasticsearch's bulk api format and bulk operation
* will fail.
*
* @author Dogukan Sonmez
* @author cihat keser
*/
public class Bulk extends AbstractAction<BulkResult> {
final static Logger log = LoggerFactory.getLogger(Bulk.class);
protected Collection<BulkableAction> bulkableActions;
protected Bulk(Builder builder) {
super(builder);
indexName = builder.defaultIndex;
typeName = builder.defaultType;
bulkableActions = builder.actions;
setURI(buildURI());
}
private Object getJson(Gson gson, Object source) {
if (source instanceof String) {
return source;
} else {
return gson.toJson(source);
}
}
@Override
public String getRestMethodName() {
return "POST";
}
@Override
public String getData(Gson gson) {
/*
{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "type1", "_id" : "2" } }
*/
StringBuilder sb = new StringBuilder();
for (BulkableAction action : bulkableActions) {
// write out the action-meta-data line
// e.g.: { "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
Map<String, Map<String, String>> opMap = new LinkedHashMap<String, Map<String, String>>(1);
Map<String, String> opDetails = new LinkedHashMap<String, String>(3);
if (!StringUtils.isBlank(action.getId())) {
opDetails.put("_id", action.getId());
}
if (!StringUtils.isBlank(action.getIndex())) {
opDetails.put("_index", action.getIndex());
}
if (!StringUtils.isBlank(action.getType())) {
opDetails.put("_type", action.getType());
}
for (String parameter : Parameters.ACCEPTED_IN_BULK) {
try {
Collection<Object> values = action.getParameter(parameter);
if (values != null) {
if (values.size() == 1) {
opDetails.put("_" + parameter, values.iterator().next().toString());
} else if (values.size() > 1) {
throw new IllegalArgumentException("Expecting a single value for '" + parameter + "' parameter, you provided: " + values.size());
}
}
} catch (NullPointerException e) {
log.debug("Could not retrieve '" + parameter + "' parameter from action.", e);
}
}
opMap.put(action.getBulkMethodName(), opDetails);
sb.append(gson.toJson(opMap, new TypeToken<Map<String, Map<String, String>>>() {
}.getType()));
sb.append("\n");
// write out the action source/document line
// e.g.: { "field1" : "value1" }
Object source = action.getData(gson);
if (source != null) {
sb.append(getJson(gson, source));
sb.append("\n");
}
}
return sb.toString();
}
@Override
public String getPathToResult() {
return "ok";
}
@Override
protected String buildURI() {
return super.buildURI() + "/_bulk";
}
@Override
public BulkResult createNewElasticSearchResult(String responseBody, int statusCode, String reasonPhrase, Gson gson) {
return createNewElasticSearchResult(new BulkResult(gson), responseBody, statusCode, reasonPhrase, gson);
}
@Override
protected BulkResult createNewElasticSearchResult(BulkResult result, String responseBody, int statusCode, String reasonPhrase, Gson gson) {
JsonObject jsonMap = parseResponseBody(responseBody);
result.setResponseCode(statusCode);
result.setJsonString(responseBody);
result.setJsonObject(jsonMap);
result.setPathToResult(getPathToResult());
if (isHttpSuccessful(statusCode)) {
if(jsonMap.has("errors") && jsonMap.get("errors").getAsBoolean())
{
result.setSucceeded(false);
result.setErrorMessage("One or more of the items in the Bulk request failed, check BulkResult.getItems() for more information.");
log.debug("Bulk operation failed due to one or more failed actions within the Bulk request");
} else {
result.setSucceeded(true);
log.debug("Bulk operation was successfull");
}
} else {
result.setSucceeded(false);
// provide the generic HTTP status code error, if one hasn't already come in via the JSON response...
// eg.
// IndicesExist will return 404 (with no content at all) for a missing index, but:
// Update will return 404 (with an error message for DocumentMissingException)
if (result.getErrorMessage() == null) {
result.setErrorMessage(statusCode + " " + (reasonPhrase == null ? "null" : reasonPhrase));
}
log.debug("Bulk operation failed with an HTTP error");
}
return result;
}
public static class Builder extends GenericResultAbstractAction.Builder<Bulk, Builder> {
private List<BulkableAction> actions = new LinkedList<BulkableAction>();
private String defaultIndex;
private String defaultType;
public Builder defaultIndex(String defaultIndex) {
this.defaultIndex = defaultIndex;
return this;
}
public Builder defaultType(String defaultType) {
this.defaultType = defaultType;
return this;
}
public Builder addAction(BulkableAction action) {
this.actions.add(action);
return this;
}
public Builder addAction(Collection<? extends BulkableAction> actions) {
this.actions.addAll(actions);
return this;
}
public Bulk build() {
return new Bulk(this);
}
}
}