View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.opensearch.log.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.log.allcommon.EsAbstractEntity.DocMeta;
29  import org.codelibs.fess.opensearch.log.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   * @param <ENTITY> The type of entity.
64   * @param <CB> The type of condition-bean.
65   * @author ESFlute (using FreeGen)
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      //                                                                       OpenSearch
93      //                                                                              ======
94      public RefreshResponse refresh() {
95          return client.admin().indices().prepareRefresh(asEsIndex()).execute().actionGet(refreshTimeout);
96      }
97  
98      // ===================================================================================
99      //                                                                              Select
100     //                                                                              ======
101     @Override
102     protected int delegateSelectCountUniquely(final ConditionBean cb) {
103         // #pending check response and cast problem
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); // immediately caught by caller and translated
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         // #pending check response
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         // #pending others
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     //                                                                              Update
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     // to suppress xacceptUpdateColumnModifiedPropertiesIfNeeds()'s specify process
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     //                                                                        Assist Logic
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 }