|
28 | 28 | import org.apache.http.entity.ContentType; |
29 | 29 | import org.apache.lucene.util.BytesRef; |
30 | 30 | import org.elasticsearch.action.DocWriteRequest; |
| 31 | +import org.elasticsearch.action.bulk.BulkRequest; |
31 | 32 | import org.elasticsearch.action.get.GetRequest; |
32 | 33 | import org.elasticsearch.action.index.IndexRequest; |
33 | 34 | import org.elasticsearch.action.support.ActiveShardCount; |
34 | 35 | import org.elasticsearch.action.support.WriteRequest; |
35 | 36 | import org.elasticsearch.action.update.UpdateRequest; |
| 37 | +import org.elasticsearch.common.Nullable; |
36 | 38 | import org.elasticsearch.common.Strings; |
| 39 | +import org.elasticsearch.common.bytes.BytesReference; |
37 | 40 | import org.elasticsearch.common.lucene.uid.Versions; |
38 | 41 | import org.elasticsearch.common.unit.TimeValue; |
| 42 | +import org.elasticsearch.common.xcontent.NamedXContentRegistry; |
| 43 | +import org.elasticsearch.common.xcontent.XContentBuilder; |
39 | 44 | import org.elasticsearch.common.xcontent.XContentHelper; |
| 45 | +import org.elasticsearch.common.xcontent.XContentParser; |
40 | 46 | import org.elasticsearch.common.xcontent.XContentType; |
41 | 47 | import org.elasticsearch.index.VersionType; |
42 | 48 | import org.elasticsearch.search.fetch.subphase.FetchSourceContext; |
43 | 49 |
|
| 50 | +import java.io.ByteArrayOutputStream; |
44 | 51 | import java.io.IOException; |
45 | 52 | import java.util.Collections; |
46 | 53 | import java.util.HashMap; |
@@ -77,6 +84,127 @@ static Request ping() { |
77 | 84 | return new Request("HEAD", "/", Collections.emptyMap(), null); |
78 | 85 | } |
79 | 86 |
|
| 87 | + static Request bulk(BulkRequest bulkRequest) throws IOException { |
| 88 | + Params parameters = Params.builder(); |
| 89 | + parameters.withTimeout(bulkRequest.timeout()); |
| 90 | + parameters.withRefreshPolicy(bulkRequest.getRefreshPolicy()); |
| 91 | + |
| 92 | + // Bulk API only supports newline delimited JSON or Smile. Before executing |
| 93 | + // the bulk, we need to check that all requests have the same content-type |
| 94 | + // and this content-type is supported by the Bulk API. |
| 95 | + XContentType bulkContentType = null; |
| 96 | + for (int i = 0; i < bulkRequest.numberOfActions(); i++) { |
| 97 | + DocWriteRequest<?> request = bulkRequest.requests().get(i); |
| 98 | + |
| 99 | + DocWriteRequest.OpType opType = request.opType(); |
| 100 | + if (opType == DocWriteRequest.OpType.INDEX || opType == DocWriteRequest.OpType.CREATE) { |
| 101 | + bulkContentType = enforceSameContentType((IndexRequest) request, bulkContentType); |
| 102 | + |
| 103 | + } else if (opType == DocWriteRequest.OpType.UPDATE) { |
| 104 | + UpdateRequest updateRequest = (UpdateRequest) request; |
| 105 | + if (updateRequest.doc() != null) { |
| 106 | + bulkContentType = enforceSameContentType(updateRequest.doc(), bulkContentType); |
| 107 | + } |
| 108 | + if (updateRequest.upsertRequest() != null) { |
| 109 | + bulkContentType = enforceSameContentType(updateRequest.upsertRequest(), bulkContentType); |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + if (bulkContentType == null) { |
| 115 | + bulkContentType = XContentType.JSON; |
| 116 | + } |
| 117 | + |
| 118 | + byte separator = bulkContentType.xContent().streamSeparator(); |
| 119 | + ContentType requestContentType = ContentType.create(bulkContentType.mediaType()); |
| 120 | + |
| 121 | + ByteArrayOutputStream content = new ByteArrayOutputStream(); |
| 122 | + for (DocWriteRequest<?> request : bulkRequest.requests()) { |
| 123 | + DocWriteRequest.OpType opType = request.opType(); |
| 124 | + |
| 125 | + try (XContentBuilder metadata = XContentBuilder.builder(bulkContentType.xContent())) { |
| 126 | + metadata.startObject(); |
| 127 | + { |
| 128 | + metadata.startObject(opType.getLowercase()); |
| 129 | + if (Strings.hasLength(request.index())) { |
| 130 | + metadata.field("_index", request.index()); |
| 131 | + } |
| 132 | + if (Strings.hasLength(request.type())) { |
| 133 | + metadata.field("_type", request.type()); |
| 134 | + } |
| 135 | + if (Strings.hasLength(request.id())) { |
| 136 | + metadata.field("_id", request.id()); |
| 137 | + } |
| 138 | + if (Strings.hasLength(request.routing())) { |
| 139 | + metadata.field("_routing", request.routing()); |
| 140 | + } |
| 141 | + if (Strings.hasLength(request.parent())) { |
| 142 | + metadata.field("_parent", request.parent()); |
| 143 | + } |
| 144 | + if (request.version() != Versions.MATCH_ANY) { |
| 145 | + metadata.field("_version", request.version()); |
| 146 | + } |
| 147 | + |
| 148 | + VersionType versionType = request.versionType(); |
| 149 | + if (versionType != VersionType.INTERNAL) { |
| 150 | + if (versionType == VersionType.EXTERNAL) { |
| 151 | + metadata.field("_version_type", "external"); |
| 152 | + } else if (versionType == VersionType.EXTERNAL_GTE) { |
| 153 | + metadata.field("_version_type", "external_gte"); |
| 154 | + } else if (versionType == VersionType.FORCE) { |
| 155 | + metadata.field("_version_type", "force"); |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + if (opType == DocWriteRequest.OpType.INDEX || opType == DocWriteRequest.OpType.CREATE) { |
| 160 | + IndexRequest indexRequest = (IndexRequest) request; |
| 161 | + if (Strings.hasLength(indexRequest.getPipeline())) { |
| 162 | + metadata.field("pipeline", indexRequest.getPipeline()); |
| 163 | + } |
| 164 | + } else if (opType == DocWriteRequest.OpType.UPDATE) { |
| 165 | + UpdateRequest updateRequest = (UpdateRequest) request; |
| 166 | + if (updateRequest.retryOnConflict() > 0) { |
| 167 | + metadata.field("_retry_on_conflict", updateRequest.retryOnConflict()); |
| 168 | + } |
| 169 | + if (updateRequest.fetchSource() != null) { |
| 170 | + metadata.field("_source", updateRequest.fetchSource()); |
| 171 | + } |
| 172 | + } |
| 173 | + metadata.endObject(); |
| 174 | + } |
| 175 | + metadata.endObject(); |
| 176 | + |
| 177 | + BytesRef metadataSource = metadata.bytes().toBytesRef(); |
| 178 | + content.write(metadataSource.bytes, metadataSource.offset, metadataSource.length); |
| 179 | + content.write(separator); |
| 180 | + } |
| 181 | + |
| 182 | + BytesRef source = null; |
| 183 | + if (opType == DocWriteRequest.OpType.INDEX || opType == DocWriteRequest.OpType.CREATE) { |
| 184 | + IndexRequest indexRequest = (IndexRequest) request; |
| 185 | + BytesReference indexSource = indexRequest.source(); |
| 186 | + XContentType indexXContentType = indexRequest.getContentType(); |
| 187 | + |
| 188 | + try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, indexSource, indexXContentType)) { |
| 189 | + try (XContentBuilder builder = XContentBuilder.builder(bulkContentType.xContent())) { |
| 190 | + builder.copyCurrentStructure(parser); |
| 191 | + source = builder.bytes().toBytesRef(); |
| 192 | + } |
| 193 | + } |
| 194 | + } else if (opType == DocWriteRequest.OpType.UPDATE) { |
| 195 | + source = XContentHelper.toXContent((UpdateRequest) request, bulkContentType, false).toBytesRef(); |
| 196 | + } |
| 197 | + |
| 198 | + if (source != null) { |
| 199 | + content.write(source.bytes, source.offset, source.length); |
| 200 | + content.write(separator); |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + HttpEntity entity = new ByteArrayEntity(content.toByteArray(), 0, content.size(), requestContentType); |
| 205 | + return new Request(HttpPost.METHOD_NAME, "/_bulk", parameters.getParams(), entity); |
| 206 | + } |
| 207 | + |
80 | 208 | static Request exists(GetRequest getRequest) { |
81 | 209 | Request request = get(getRequest); |
82 | 210 | return new Request(HttpHead.METHOD_NAME, request.endpoint, request.params, null); |
@@ -312,4 +440,26 @@ static Params builder() { |
312 | 440 | return new Params(); |
313 | 441 | } |
314 | 442 | } |
| 443 | + |
| 444 | + /** |
| 445 | + * Ensure that the {@link IndexRequest}'s content type is supported by the Bulk API and that it conforms |
| 446 | + * to the current {@link BulkRequest}'s content type (if it's known at the time of this method get called). |
| 447 | + * |
| 448 | + * @return the {@link IndexRequest}'s content type |
| 449 | + */ |
| 450 | + static XContentType enforceSameContentType(IndexRequest indexRequest, @Nullable XContentType xContentType) { |
| 451 | + XContentType requestContentType = indexRequest.getContentType(); |
| 452 | + if (requestContentType != XContentType.JSON && requestContentType != XContentType.SMILE) { |
| 453 | + throw new IllegalArgumentException("Unsupported content-type found for request with content-type [" + requestContentType |
| 454 | + + "], only JSON and SMILE are supported"); |
| 455 | + } |
| 456 | + if (xContentType == null) { |
| 457 | + return requestContentType; |
| 458 | + } |
| 459 | + if (requestContentType != xContentType) { |
| 460 | + throw new IllegalArgumentException("Mismatching content-type found for request with content-type [" + requestContentType |
| 461 | + + "], previous requests have content-type [" + xContentType + "]"); |
| 462 | + } |
| 463 | + return xContentType; |
| 464 | + } |
315 | 465 | } |
0 commit comments