1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.opensearch.config.allcommon;
17
18 import java.time.LocalDateTime;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Date;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.Map;
26 import java.util.function.Function;
27
28 import org.codelibs.fess.opensearch.config.allcommon.EsAbstractEntity.DocMeta;
29 import org.codelibs.fess.opensearch.config.allcommon.EsAbstractEntity.RequestOptionCall;
30 import org.dbflute.Entity;
31 import org.dbflute.bhv.AbstractBehaviorWritable;
32 import org.dbflute.bhv.readable.EntityRowHandler;
33 import org.dbflute.bhv.writable.DeleteOption;
34 import org.dbflute.bhv.writable.InsertOption;
35 import org.dbflute.bhv.writable.UpdateOption;
36 import org.dbflute.cbean.ConditionBean;
37 import org.dbflute.cbean.coption.CursorSelectOption;
38 import org.dbflute.cbean.result.ListResultBean;
39 import org.dbflute.exception.FetchingOverSafetySizeException;
40 import org.dbflute.exception.IllegalBehaviorStateException;
41 import org.dbflute.util.DfTypeUtil;
42 import org.opensearch.action.DocWriteResponse.Result;
43 import org.opensearch.action.admin.indices.refresh.RefreshResponse;
44 import org.opensearch.action.bulk.BulkItemResponse;
45 import org.opensearch.action.bulk.BulkRequestBuilder;
46 import org.opensearch.action.bulk.BulkResponse;
47 import org.opensearch.action.delete.DeleteRequestBuilder;
48 import org.opensearch.action.delete.DeleteResponse;
49 import org.opensearch.action.index.IndexRequestBuilder;
50 import org.opensearch.action.index.IndexResponse;
51 import org.opensearch.action.search.SearchRequestBuilder;
52 import org.opensearch.action.search.SearchResponse;
53 import org.opensearch.action.update.UpdateRequestBuilder;
54 import org.opensearch.core.action.ActionListener;
55 import org.opensearch.index.seqno.SequenceNumbers;
56 import org.opensearch.search.SearchHit;
57 import org.opensearch.search.SearchHits;
58 import org.opensearch.transport.client.Client;
59
60 import jakarta.annotation.Resource;
61
62
63
64
65
66
67 public abstract class EsAbstractBehavior<ENTITY extends Entity, CB extends ConditionBean> extends AbstractBehaviorWritable<ENTITY, CB> {
68
69 @Resource
70 private Client client;
71
72 protected int sizeForDelete = 100;
73 protected String scrollForDelete = "1m";
74 protected int sizeForCursor = 100;
75 protected String scrollForCursor = "1m";
76 protected String searchTimeout = "3m";
77 protected String indexTimeout = "3m";
78 protected String scrollSearchTimeout = "3m";
79 protected String bulkTimeout = "3m";
80 protected String deleteTimeout = "3m";
81 protected String refreshTimeout = "1m";
82
83 protected abstract String asEsIndex();
84
85 protected abstract String asEsIndexType();
86
87 protected abstract String asEsSearchType();
88
89 protected abstract <RESULT extends ENTITY> RESULT createEntity(Map<String, Object> source, Class<? extends RESULT> entityType);
90
91
92
93
94 public RefreshResponse refresh() {
95 return client.admin().indices().prepareRefresh(asEsIndex()).execute().actionGet(refreshTimeout);
96 }
97
98
99
100
101 @Override
102 protected int delegateSelectCountUniquely(final ConditionBean cb) {
103
104 final SearchRequestBuilder builder = client.prepareSearch(asEsIndex());
105 final EsAbstractConditionBean esCb = (EsAbstractConditionBean) cb;
106 if (esCb.getPreference() != null) {
107 builder.setPreference(esCb.getPreference());
108 }
109 return (int) getSearchHits(esCb.build(builder).execute().actionGet(searchTimeout)).getTotalHits().value();
110 }
111
112 @Override
113 protected <RESULT extends ENTITY> RESULT delegateSelectEntity(final ConditionBean cb, final Class<? extends RESULT> entityType) {
114 final List<? extends RESULT> list = delegateSelectList(cb, entityType);
115 if (list.isEmpty()) {
116 return null;
117 }
118 if (list.size() >= 2) {
119 String msg = "The size of selected list is over 1: " + list.size();
120 throw new FetchingOverSafetySizeException(msg, 1);
121 }
122 return list.get(0);
123 }
124
125 @Override
126 protected <RESULT extends ENTITY> List<RESULT> delegateSelectList(final ConditionBean cb, final Class<? extends RESULT> entityType) {
127
128 final SearchRequestBuilder builder = client.prepareSearch(asEsIndex());
129 final int from;
130 final int size;
131 if (cb.isFetchScopeEffective()) {
132 from = cb.getPageStartIndex();
133 size = cb.getFetchSize();
134 } else {
135 from = 0;
136 size = 10;
137 }
138 builder.setFrom(from);
139 builder.setSize(size);
140 final EsAbstractConditionBean esCb = (EsAbstractConditionBean) cb;
141 if (esCb.getPreference() != null) {
142 builder.setPreference(esCb.getPreference());
143 }
144 esCb.request().build(builder);
145 final SearchResponse response = esCb.build(builder).execute().actionGet(searchTimeout);
146
147 final EsPagingResultBean<RESULT> list = new EsPagingResultBean<>(builder);
148 final SearchHits searchHits = getSearchHits(response);
149 searchHits.forEach(hit -> {
150 final Map<String, Object> source = hit.getSourceAsMap();
151 final RESULT entity = createEntity(source, entityType);
152 final DocMeta docMeta = ((EsAbstractEntity) entity).asDocMeta();
153 docMeta.id(hit.getId());
154 docMeta.version(hit.getVersion());
155 docMeta.seqNo(hit.getSeqNo());
156 docMeta.primaryTerm(hit.getPrimaryTerm());
157 list.add(entity);
158 });
159
160 list.setPageSize(size);
161 list.setAllRecordCount((int) searchHits.getTotalHits().value());
162 list.setCurrentPageNumber(cb.getFetchPageNumber());
163
164 list.setTook(response.getTook().getMillis());
165 list.setTotalShards(response.getTotalShards());
166 list.setSuccessfulShards(response.getSuccessfulShards());
167 list.setFailedShards(response.getFailedShards());
168 list.setTotalHits(searchHits.getTotalHits());
169
170 list.setAggregation(response.getAggregations());
171
172
173
174 return list;
175 }
176
177 @Override
178 protected <RESULT extends ENTITY> void helpSelectCursorHandlingByPaging(CB cb, EntityRowHandler<RESULT> handler,
179 Class<? extends RESULT> entityType, CursorSelectOption option) {
180 delegateSelectCursor(cb, handler, entityType);
181 }
182
183 @Override
184 protected <RESULT extends ENTITY> void delegateSelectCursor(final ConditionBean cb, final EntityRowHandler<RESULT> handler,
185 final Class<? extends RESULT> entityType) {
186 delegateBulkRequest(cb, searchHits -> {
187 searchHits.forEach(hit -> {
188 if (handler.isBreakCursor()) {
189 return;
190 }
191 final Map<String, Object> source = hit.getSourceAsMap();
192 final RESULT entity = createEntity(source, entityType);
193 final DocMeta docMeta = ((EsAbstractEntity) entity).asDocMeta();
194 docMeta.id(hit.getId());
195 docMeta.version(hit.getVersion());
196 docMeta.seqNo(hit.getSeqNo());
197 docMeta.primaryTerm(hit.getPrimaryTerm());
198 handler.handle(entity);
199 });
200
201 return !handler.isBreakCursor();
202 });
203 }
204
205 protected <RESULT extends ENTITY> void delegateSelectBulk(final ConditionBean cb, final EntityRowHandler<List<RESULT>> handler,
206 final Class<? extends RESULT> entityType) {
207 assertCBStateValid(cb);
208 assertObjectNotNull("entityRowHandler", handler);
209 assertSpecifyDerivedReferrerEntityProperty(cb, entityType);
210 assertObjectNotNull("entityRowHandler", handler);
211 delegateBulkRequest(cb, searchHits -> {
212 List<RESULT> list = new ArrayList<>();
213 searchHits.forEach(hit -> {
214 final Map<String, Object> source = hit.getSourceAsMap();
215 final RESULT entity = createEntity(source, entityType);
216 final DocMeta docMeta = ((EsAbstractEntity) entity).asDocMeta();
217 docMeta.id(hit.getId());
218 docMeta.version(hit.getVersion());
219 docMeta.seqNo(hit.getSeqNo());
220 docMeta.primaryTerm(hit.getPrimaryTerm());
221 list.add(entity);
222 });
223
224 handler.handle(list);
225 return !handler.isBreakCursor();
226 });
227 }
228
229 protected void delegateBulkRequest(final ConditionBean cb, Function<SearchHits, Boolean> handler) {
230 final SearchRequestBuilder builder = client.prepareSearch(asEsIndex()).setScroll(scrollForCursor).setSize(sizeForCursor);
231 final EsAbstractConditionBean esCb = (EsAbstractConditionBean) cb;
232 if (esCb.getPreference() != null) {
233 builder.setPreference(esCb.getPreference());
234 }
235 esCb.request().build(builder);
236 SearchResponse response = esCb.build(builder).execute().actionGet(scrollSearchTimeout);
237 String scrollId = response.getScrollId();
238 try {
239 while (scrollId != null) {
240 final SearchHits searchHits = getSearchHits(response);
241 final SearchHit[] hits = searchHits.getHits();
242 if (hits.length == 0) {
243 break;
244 }
245
246 if (!handler.apply(searchHits)) {
247 break;
248 }
249
250 response = client.prepareSearchScroll(scrollId).setScroll(scrollForDelete).execute().actionGet(scrollSearchTimeout);
251 if (!scrollId.equals(response.getScrollId())) {
252 deleteScrollContext(scrollId);
253 }
254 scrollId = response.getScrollId();
255 }
256 } finally {
257 deleteScrollContext(scrollId);
258 }
259 }
260
261 protected void deleteScrollContext(final String scrollId) {
262 if (scrollId != null) {
263 client.prepareClearScroll().addScrollId(scrollId).execute(ActionListener.wrap(() -> {}));
264 }
265 }
266
267 @Override
268 protected Number doReadNextVal() {
269 final String msg = "This table is NOT related to sequence: " + asEsIndexType();
270 throw new UnsupportedOperationException(msg);
271 }
272
273 @Override
274 protected <RESULT extends Entity> ListResultBean<RESULT> createListResultBean(final ConditionBean cb, final List<RESULT> selectedList) {
275 if (selectedList instanceof EsPagingResultBean) {
276 return (ListResultBean<RESULT>) selectedList;
277 }
278 throw new IllegalBehaviorStateException("selectedList is not EsPagingResultBean.");
279 }
280
281
282
283
284 @Override
285 protected int delegateInsert(final Entity entity, final InsertOption<? extends ConditionBean> option) {
286 final EsAbstractEntity esEntity = (EsAbstractEntity) entity;
287 IndexRequestBuilder builder = createInsertRequest(esEntity);
288
289 final IndexResponse response = builder.execute().actionGet(indexTimeout);
290 esEntity.asDocMeta().id(response.getId());
291 return response.getResult() == Result.CREATED ? 1 : 0;
292 }
293
294 protected IndexRequestBuilder createInsertRequest(final EsAbstractEntity esEntity) {
295 final IndexRequestBuilder builder = client.prepareIndex().setIndex(asEsIndex()).setSource(toSource(esEntity));
296 final String id = esEntity.asDocMeta().id();
297 if (id != null) {
298 builder.setId(id);
299 }
300 final RequestOptionCall<IndexRequestBuilder> indexOption = esEntity.asDocMeta().indexOption();
301 if (indexOption != null) {
302 indexOption.callback(builder);
303 }
304 return builder;
305 }
306
307 @Override
308 protected int delegateUpdate(final Entity entity, final UpdateOption<? extends ConditionBean> option) {
309 final EsAbstractEntity esEntity = (EsAbstractEntity) entity;
310 final IndexRequestBuilder builder = createUpdateRequest(esEntity);
311
312 final IndexResponse response = builder.execute().actionGet(indexTimeout);
313 final long seqNo = response.getSeqNo();
314 if (seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
315 esEntity.asDocMeta().seqNo(seqNo);
316 }
317 final long primaryTerm = response.getPrimaryTerm();
318 if (primaryTerm != SequenceNumbers.UNASSIGNED_PRIMARY_TERM) {
319 esEntity.asDocMeta().primaryTerm(primaryTerm);
320 }
321
322 return 1;
323 }
324
325 protected IndexRequestBuilder createUpdateRequest(final EsAbstractEntity esEntity) {
326 final IndexRequestBuilder builder =
327 client.prepareIndex().setIndex(asEsIndex()).setId(esEntity.asDocMeta().id()).setSource(toSource(esEntity));
328 final RequestOptionCall<IndexRequestBuilder> indexOption = esEntity.asDocMeta().indexOption();
329 if (indexOption != null) {
330 indexOption.callback(builder);
331 }
332 final Long seqNo = esEntity.asDocMeta().seqNo();
333 if (seqNo != null && seqNo.longValue() != SequenceNumbers.UNASSIGNED_SEQ_NO) {
334 esEntity.asDocMeta().seqNo(seqNo);
335 }
336 final Long primaryTerm = esEntity.asDocMeta().primaryTerm();
337 if (primaryTerm != null && primaryTerm.longValue() != SequenceNumbers.UNASSIGNED_PRIMARY_TERM) {
338 esEntity.asDocMeta().primaryTerm(primaryTerm);
339 }
340 return builder;
341 }
342
343 protected Map<String, Object> toSource(final EsAbstractEntity esEntity) {
344 return esEntity.toSource();
345 }
346
347 @Override
348 protected int delegateDelete(final Entity entity, final DeleteOption<? extends ConditionBean> option) {
349 final EsAbstractEntity esEntity = (EsAbstractEntity) entity;
350 final DeleteRequestBuilder builder = createDeleteRequest(esEntity);
351
352 final DeleteResponse response = builder.execute().actionGet(deleteTimeout);
353 return response.getResult() == Result.DELETED ? 1 : 0;
354 }
355
356 protected DeleteRequestBuilder createDeleteRequest(final EsAbstractEntity esEntity) {
357 final DeleteRequestBuilder builder = client.prepareDelete().setIndex(asEsIndex()).setId(esEntity.asDocMeta().id());
358 final RequestOptionCall<DeleteRequestBuilder> deleteOption = esEntity.asDocMeta().deleteOption();
359 if (deleteOption != null) {
360 deleteOption.callback(builder);
361 }
362 return builder;
363 }
364
365 @Override
366 protected int delegateQueryDelete(final ConditionBean cb, final DeleteOption<? extends ConditionBean> option) {
367 final SearchRequestBuilder builder = client.prepareSearch(asEsIndex()).setScroll(scrollForDelete).setSize(sizeForDelete);
368 final EsAbstractConditionBean esCb = (EsAbstractConditionBean) cb;
369 if (esCb.getPreference() != null) {
370 esCb.setPreference(esCb.getPreference());
371 }
372 esCb.request().build(builder);
373 SearchResponse response = esCb.build(builder).execute().actionGet(scrollSearchTimeout);
374 String scrollId = response.getScrollId();
375 int count = 0;
376 try {
377 while (scrollId != null) {
378 final SearchHits searchHits = getSearchHits(response);
379 final SearchHit[] hits = searchHits.getHits();
380 if (hits.length == 0) {
381 break;
382 }
383
384 final BulkRequestBuilder bulkRequest = client.prepareBulk();
385 for (final SearchHit hit : hits) {
386 bulkRequest.add(client.prepareDelete().setIndex(asEsIndex()).setId(hit.getId()));
387 }
388 count += hits.length;
389 final BulkResponse bulkResponse = bulkRequest.execute().actionGet(bulkTimeout);
390 if (bulkResponse.hasFailures()) {
391 throw new IllegalBehaviorStateException(bulkResponse.buildFailureMessage());
392 }
393
394 response = client.prepareSearchScroll(scrollId).setScroll(scrollForDelete).execute().actionGet(scrollSearchTimeout);
395 if (!scrollId.equals(response.getScrollId())) {
396 deleteScrollContext(scrollId);
397 }
398 }
399 } finally {
400 deleteScrollContext(scrollId);
401 }
402 return count;
403 }
404
405 protected int[] delegateBatchInsert(final List<? extends Entity> entityList, final InsertOption<? extends ConditionBean> option) {
406 if (entityList.isEmpty()) {
407 return new int[] {};
408 }
409 return delegateBatchRequest(entityList, esEntity -> {
410 return createInsertRequest(esEntity);
411 });
412 }
413
414 protected int[] delegateBatchUpdate(List<? extends Entity> entityList, UpdateOption<? extends ConditionBean> option) {
415 if (entityList.isEmpty()) {
416 return new int[] {};
417 }
418 return delegateBatchRequest(entityList, esEntity -> {
419 return createUpdateRequest(esEntity);
420 });
421 }
422
423 protected int[] delegateBatchDelete(List<? extends Entity> entityList, DeleteOption<? extends ConditionBean> option) {
424 if (entityList.isEmpty()) {
425 return new int[] {};
426 }
427 return delegateBatchRequest(entityList, esEntity -> {
428 return createDeleteRequest(esEntity);
429 });
430 }
431
432 protected <BUILDER> int[] delegateBatchRequest(final List<? extends Entity> entityList, Function<EsAbstractEntity, BUILDER> call) {
433 @SuppressWarnings("unchecked")
434 final BulkList<? extends Entity, BUILDER> bulkList = (BulkList<? extends Entity, BUILDER>) entityList;
435 final RequestOptionCall<BUILDER> builderEntityCall = bulkList.getEntityCall();
436 final BulkRequestBuilder bulkBuilder = client.prepareBulk();
437 for (final Entity entity : entityList) {
438 final EsAbstractEntity esEntity = (EsAbstractEntity) entity;
439 BUILDER builder = call.apply(esEntity);
440 if (builder instanceof IndexRequestBuilder) {
441 if (builderEntityCall != null) {
442 builderEntityCall.callback(builder);
443 }
444 bulkBuilder.add((IndexRequestBuilder) builder);
445 } else if (builder instanceof UpdateRequestBuilder) {
446 if (builderEntityCall != null) {
447 builderEntityCall.callback(builder);
448 }
449 bulkBuilder.add((UpdateRequestBuilder) builder);
450 } else if (builder instanceof DeleteRequestBuilder) {
451 if (builderEntityCall != null) {
452 builderEntityCall.callback(builder);
453 }
454 bulkBuilder.add((DeleteRequestBuilder) builder);
455 }
456 }
457 final RequestOptionCall<BulkRequestBuilder> builderCall = bulkList.getCall();
458 if (builderCall != null) {
459 builderCall.callback(bulkBuilder);
460 }
461
462 final BulkResponse response = bulkBuilder.execute().actionGet(bulkTimeout);
463 final BulkItemResponse[] itemResponses = response.getItems();
464 if (itemResponses.length != entityList.size()) {
465 throw new IllegalStateException("Invalid response size: " + itemResponses.length + " != " + entityList.size());
466 }
467 final int[] results = new int[itemResponses.length];
468 for (int i = 0; i < itemResponses.length; i++) {
469 final BulkItemResponse itemResponse = itemResponses[i];
470 final Entity entity = entityList.get(i);
471 if (entity instanceof EsAbstractEntity) {
472 ((EsAbstractEntity) entity).asDocMeta().id(itemResponse.getId());
473 }
474 results[i] = itemResponse.isFailed() ? 0 : 1;
475 }
476 return results;
477 }
478
479
480 @Override
481 protected UpdateOption<CB> createPlainUpdateOption() {
482 UpdateOption<CB> updateOption = new UpdateOption<CB>();
483 updateOption.xtoBeCompatibleBatchUpdateDefaultEveryColumn();
484 return updateOption;
485 }
486
487 protected boolean isCompatibleBatchInsertDefaultEveryColumn() {
488 return true;
489 }
490
491 public void setSizeForDelete(int sizeForDelete) {
492 this.sizeForDelete = sizeForDelete;
493 }
494
495 public void setScrollForDelete(String scrollForDelete) {
496 this.scrollForDelete = scrollForDelete;
497 }
498
499 public void setSizeForCursor(int sizeForCursor) {
500 this.sizeForCursor = sizeForCursor;
501 }
502
503 public void setScrollForCursor(String scrollForCursor) {
504 this.scrollForCursor = scrollForCursor;
505 }
506
507 public void setSearchTimeout(String searchTimeout) {
508 this.searchTimeout = searchTimeout;
509 }
510
511 public void setIndexTimeout(String indexTimeout) {
512 this.indexTimeout = indexTimeout;
513 }
514
515 public void setScrollSearchTimeout(String scrollSearchTimeout) {
516 this.scrollSearchTimeout = scrollSearchTimeout;
517 }
518
519 public void setBulkTimeout(String bulkTimeout) {
520 this.bulkTimeout = bulkTimeout;
521 }
522
523 public void setDeleteTimeout(String deleteTimeout) {
524 this.deleteTimeout = deleteTimeout;
525 }
526
527 public void setRefreshTimeout(String refreshTimeout) {
528 this.refreshTimeout = refreshTimeout;
529 }
530
531
532
533
534 protected String[] toStringArray(final Object value) {
535 if (value instanceof String[]) {
536 return (String[]) value;
537 } else if (value instanceof List) {
538 return ((List<?>) value).stream().map(v -> v.toString()).toArray(n -> new String[n]);
539 }
540 String str = DfTypeUtil.toString(value);
541 if (str == null) {
542 return null;
543 }
544 return new String[] { str };
545 }
546
547 protected LocalDateTime toLocalDateTime(Object value) {
548 return DfTypeUtil.toLocalDateTime(value);
549 }
550
551 protected Date toDate(Object value) {
552 return DfTypeUtil.toDate(value);
553 }
554
555 protected SearchHits getSearchHits(final SearchResponse response) {
556 SearchHits hits = response.getHits();
557 if (hits == null) {
558 throw new IllegalBehaviorStateException("hits is null: " + response);
559 }
560 return hits;
561 }
562
563 public static class BulkList<E, B> implements List<E> {
564
565 private final List<E> parent;
566
567 private final RequestOptionCall<BulkRequestBuilder> call;
568
569 private final RequestOptionCall<B> entityCall;
570
571 public BulkList(final List<E> parent, final RequestOptionCall<BulkRequestBuilder> call, final RequestOptionCall<B> entityCall) {
572 this.parent = parent;
573 this.entityCall = entityCall;
574 this.call = call;
575 }
576
577 public int size() {
578 return parent.size();
579 }
580
581 public boolean isEmpty() {
582 return parent.isEmpty();
583 }
584
585 public boolean contains(final Object o) {
586 return parent.contains(o);
587 }
588
589 public Iterator<E> iterator() {
590 return parent.iterator();
591 }
592
593 public Object[] toArray() {
594 return parent.toArray();
595 }
596
597 public <T> T[] toArray(final T[] a) {
598 return parent.toArray(a);
599 }
600
601 public boolean add(final E e) {
602 return parent.add(e);
603 }
604
605 public boolean remove(final Object o) {
606 return parent.remove(o);
607 }
608
609 public boolean containsAll(final Collection<?> c) {
610 return parent.containsAll(c);
611 }
612
613 public boolean addAll(final Collection<? extends E> c) {
614 return parent.addAll(c);
615 }
616
617 public boolean addAll(final int index, final Collection<? extends E> c) {
618 return parent.addAll(index, c);
619 }
620
621 public boolean removeAll(final Collection<?> c) {
622 return parent.removeAll(c);
623 }
624
625 public boolean retainAll(final Collection<?> c) {
626 return parent.retainAll(c);
627 }
628
629 public void clear() {
630 parent.clear();
631 }
632
633 public boolean equals(final Object o) {
634 return parent.equals(o);
635 }
636
637 public int hashCode() {
638 return parent.hashCode();
639 }
640
641 public E get(final int index) {
642 return parent.get(index);
643 }
644
645 public E set(final int index, final E element) {
646 return parent.set(index, element);
647 }
648
649 public void add(final int index, final E element) {
650 parent.add(index, element);
651 }
652
653 public E remove(final int index) {
654 return parent.remove(index);
655 }
656
657 public int indexOf(final Object o) {
658 return parent.indexOf(o);
659 }
660
661 public int lastIndexOf(final Object o) {
662 return parent.lastIndexOf(o);
663 }
664
665 public ListIterator<E> listIterator() {
666 return parent.listIterator();
667 }
668
669 public ListIterator<E> listIterator(final int index) {
670 return parent.listIterator(index);
671 }
672
673 public List<E> subList(final int fromIndex, final int toIndex) {
674 return parent.subList(fromIndex, toIndex);
675 }
676
677 public RequestOptionCall<BulkRequestBuilder> getCall() {
678 return call;
679 }
680
681 public RequestOptionCall<B> getEntityCall() {
682 return entityCall;
683 }
684 }
685 }