View Javadoc
1   /*
2    * Copyright 2012-2017 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.es.client;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  import static org.codelibs.elasticsearch.runner.ElasticsearchClusterRunner.newConfigs;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.InetAddress;
24  import java.net.UnknownHostException;
25  import java.nio.charset.StandardCharsets;
26  import java.text.SimpleDateFormat;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.regex.Pattern;
35  import java.util.stream.Collectors;
36  
37  import javax.annotation.PostConstruct;
38  import javax.annotation.PreDestroy;
39  
40  import org.codelibs.core.beans.util.BeanUtil;
41  import org.codelibs.core.exception.ResourceNotFoundRuntimeException;
42  import org.codelibs.core.io.FileUtil;
43  import org.codelibs.core.io.ResourceUtil;
44  import org.codelibs.core.lang.StringUtil;
45  import org.codelibs.elasticsearch.runner.ElasticsearchClusterRunner;
46  import org.codelibs.elasticsearch.runner.ElasticsearchClusterRunner.Configs;
47  import org.codelibs.elasticsearch.runner.net.Curl;
48  import org.codelibs.elasticsearch.runner.net.CurlResponse;
49  import org.codelibs.fess.Constants;
50  import org.codelibs.fess.entity.FacetInfo;
51  import org.codelibs.fess.entity.GeoInfo;
52  import org.codelibs.fess.entity.PingResponse;
53  import org.codelibs.fess.entity.QueryContext;
54  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
55  import org.codelibs.fess.exception.FessSystemException;
56  import org.codelibs.fess.exception.InvalidQueryException;
57  import org.codelibs.fess.exception.ResultOffsetExceededException;
58  import org.codelibs.fess.exception.SearchQueryException;
59  import org.codelibs.fess.helper.DocumentHelper;
60  import org.codelibs.fess.helper.QueryHelper;
61  import org.codelibs.fess.mylasta.direction.FessConfig;
62  import org.codelibs.fess.util.ComponentUtil;
63  import org.codelibs.fess.util.DocMap;
64  import org.dbflute.exception.IllegalBehaviorStateException;
65  import org.dbflute.optional.OptionalEntity;
66  import org.elasticsearch.ElasticsearchException;
67  import org.elasticsearch.action.Action;
68  import org.elasticsearch.action.ActionFuture;
69  import org.elasticsearch.action.ActionListener;
70  import org.elasticsearch.action.ActionRequest;
71  import org.elasticsearch.action.ActionRequestBuilder;
72  import org.elasticsearch.action.ActionResponse;
73  import org.elasticsearch.action.DocWriteRequest;
74  import org.elasticsearch.action.DocWriteRequest.OpType;
75  import org.elasticsearch.action.DocWriteResponse.Result;
76  import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
77  import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
78  import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
79  import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
80  import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
81  import org.elasticsearch.action.admin.indices.flush.FlushResponse;
82  import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
83  import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
84  import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
85  import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
86  import org.elasticsearch.action.bulk.BulkItemResponse;
87  import org.elasticsearch.action.bulk.BulkItemResponse.Failure;
88  import org.elasticsearch.action.bulk.BulkRequest;
89  import org.elasticsearch.action.bulk.BulkRequestBuilder;
90  import org.elasticsearch.action.bulk.BulkResponse;
91  import org.elasticsearch.action.delete.DeleteRequest;
92  import org.elasticsearch.action.delete.DeleteRequestBuilder;
93  import org.elasticsearch.action.delete.DeleteResponse;
94  import org.elasticsearch.action.explain.ExplainRequest;
95  import org.elasticsearch.action.explain.ExplainRequestBuilder;
96  import org.elasticsearch.action.explain.ExplainResponse;
97  import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
98  import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequestBuilder;
99  import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
100 import org.elasticsearch.action.fieldstats.FieldStatsRequest;
101 import org.elasticsearch.action.fieldstats.FieldStatsRequestBuilder;
102 import org.elasticsearch.action.fieldstats.FieldStatsResponse;
103 import org.elasticsearch.action.get.GetRequest;
104 import org.elasticsearch.action.get.GetRequestBuilder;
105 import org.elasticsearch.action.get.GetResponse;
106 import org.elasticsearch.action.get.MultiGetRequest;
107 import org.elasticsearch.action.get.MultiGetRequestBuilder;
108 import org.elasticsearch.action.get.MultiGetResponse;
109 import org.elasticsearch.action.index.IndexRequest;
110 import org.elasticsearch.action.index.IndexRequestBuilder;
111 import org.elasticsearch.action.index.IndexResponse;
112 import org.elasticsearch.action.search.ClearScrollRequest;
113 import org.elasticsearch.action.search.ClearScrollRequestBuilder;
114 import org.elasticsearch.action.search.ClearScrollResponse;
115 import org.elasticsearch.action.search.MultiSearchRequest;
116 import org.elasticsearch.action.search.MultiSearchRequestBuilder;
117 import org.elasticsearch.action.search.MultiSearchResponse;
118 import org.elasticsearch.action.search.SearchPhaseExecutionException;
119 import org.elasticsearch.action.search.SearchRequest;
120 import org.elasticsearch.action.search.SearchRequestBuilder;
121 import org.elasticsearch.action.search.SearchResponse;
122 import org.elasticsearch.action.search.SearchScrollRequest;
123 import org.elasticsearch.action.search.SearchScrollRequestBuilder;
124 import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
125 import org.elasticsearch.action.termvectors.MultiTermVectorsRequest;
126 import org.elasticsearch.action.termvectors.MultiTermVectorsRequestBuilder;
127 import org.elasticsearch.action.termvectors.MultiTermVectorsResponse;
128 import org.elasticsearch.action.termvectors.TermVectorsRequest;
129 import org.elasticsearch.action.termvectors.TermVectorsRequestBuilder;
130 import org.elasticsearch.action.termvectors.TermVectorsResponse;
131 import org.elasticsearch.action.update.UpdateRequest;
132 import org.elasticsearch.action.update.UpdateRequestBuilder;
133 import org.elasticsearch.action.update.UpdateResponse;
134 import org.elasticsearch.client.AdminClient;
135 import org.elasticsearch.client.Client;
136 import org.elasticsearch.client.transport.TransportClient;
137 import org.elasticsearch.cluster.metadata.MappingMetaData;
138 import org.elasticsearch.common.collect.ImmutableOpenMap;
139 import org.elasticsearch.common.settings.Settings;
140 import org.elasticsearch.common.settings.Settings.Builder;
141 import org.elasticsearch.common.transport.InetSocketTransportAddress;
142 import org.elasticsearch.common.transport.TransportAddress;
143 import org.elasticsearch.common.unit.TimeValue;
144 import org.elasticsearch.common.xcontent.XContentFactory;
145 import org.elasticsearch.index.query.InnerHitBuilder;
146 import org.elasticsearch.index.query.QueryBuilder;
147 import org.elasticsearch.index.query.QueryBuilders;
148 import org.elasticsearch.search.SearchHit;
149 import org.elasticsearch.search.SearchHitField;
150 import org.elasticsearch.search.SearchHits;
151 import org.elasticsearch.search.aggregations.AggregationBuilders;
152 import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
153 import org.elasticsearch.search.aggregations.bucket.terms.Terms.Order;
154 import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
155 import org.elasticsearch.search.collapse.CollapseBuilder;
156 import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
157 import org.elasticsearch.threadpool.ThreadPool;
158 import org.elasticsearch.transport.client.PreBuiltTransportClient;
159 import org.lastaflute.core.message.UserMessages;
160 import org.slf4j.Logger;
161 import org.slf4j.LoggerFactory;
162 
163 import com.fasterxml.jackson.core.type.TypeReference;
164 import com.fasterxml.jackson.databind.ObjectMapper;
165 import com.google.common.io.BaseEncoding;
166 
167 public class FessEsClient implements Client {
168     private static final Logger logger = LoggerFactory.getLogger(FessEsClient.class);
169 
170     protected ElasticsearchClusterRunner runner;
171 
172     protected List<TransportAddress> transportAddressList = new ArrayList<>();
173 
174     protected Client client;
175 
176     protected Map<String, String> settings;
177 
178     protected String indexConfigPath = "fess_indices";
179 
180     protected List<String> indexConfigList = new ArrayList<>();
181 
182     protected Map<String, List<String>> configListMap = new HashMap<>();
183 
184     protected int sizeForDelete = 100;
185 
186     protected String scrollForDelete = "1m";
187 
188     public void addIndexConfig(final String path) {
189         indexConfigList.add(path);
190     }
191 
192     public void addConfigFile(final String index, final String path) {
193         List<String> list = configListMap.get(index);
194         if (list == null) {
195             list = new ArrayList<>();
196             configListMap.put(index, list);
197         }
198         list.add(path);
199     }
200 
201     public void setSettings(final Map<String, String> settings) {
202         this.settings = settings;
203     }
204 
205     public String getStatus() {
206         return admin().cluster().prepareHealth().execute().actionGet(ComponentUtil.getFessConfig().getIndexHealthTimeout()).getStatus()
207                 .name();
208     }
209 
210     public void setRunner(final ElasticsearchClusterRunner runner) {
211         this.runner = runner;
212     }
213 
214     public boolean isEmbedded() {
215         return this.runner != null;
216     }
217 
218     public void addTransportAddress(final String host, final int port) {
219         try {
220             transportAddressList.add(new InetSocketTransportAddress(InetAddress.getByName(host), port));
221         } catch (final UnknownHostException e) {
222             throw new FessSystemException("Failed to resolve the hostname: " + host, e);
223         }
224     }
225 
226     @PostConstruct
227     public void open() {
228         final FessConfig fessConfig = ComponentUtil.getFessConfig();
229 
230         final String transportAddressesValue = System.getProperty(Constants.FESS_ES_TRANSPORT_ADDRESSES);
231         if (StringUtil.isNotBlank(transportAddressesValue)) {
232             for (final String transportAddressValue : transportAddressesValue.split(",")) {
233                 final String[] addressPair = transportAddressValue.trim().split(":");
234                 if (addressPair.length < 3) {
235                     final String host = addressPair[0];
236                     int port = 9300;
237                     if (addressPair.length == 2) {
238                         port = Integer.parseInt(addressPair[1]);
239                     }
240                     addTransportAddress(host, port);
241                 } else {
242                     logger.warn("Invalid address format: " + transportAddressValue);
243                 }
244             }
245         }
246 
247         if (transportAddressList.isEmpty()) {
248             if (runner == null) {
249                 runner = new ElasticsearchClusterRunner();
250                 final Configs config = newConfigs().clusterName(fessConfig.getElasticsearchClusterName()).numOfNode(1).useLogger();
251                 final String esDir = System.getProperty("fess.es.dir");
252                 if (esDir != null) {
253                     config.basePath(esDir);
254                 }
255                 config.disableESLogger();
256                 runner.onBuild((number, settingsBuilder) -> {
257                     final File pluginDir = new File(esDir, "plugins");
258                     if (pluginDir.isDirectory()) {
259                         settingsBuilder.put("path.plugins", pluginDir.getAbsolutePath());
260                     } else {
261                         settingsBuilder.put("path.plugins", new File(System.getProperty("user.dir"), "plugins").getAbsolutePath());
262                     }
263                     if (settings != null) {
264                         settingsBuilder.put(settings);
265                     }
266                 });
267                 runner.build(config);
268             }
269             client = runner.client();
270             addTransportAddress("localhost", runner.node().settings().getAsInt("transport.tcp.port", 9300));
271         } else {
272             final Builder settingsBuilder = Settings.builder();
273             settingsBuilder.put("cluster.name", fessConfig.getElasticsearchClusterName());
274             settingsBuilder.put("client.transport.sniff", fessConfig.isElasticsearchTransportSniff());
275             settingsBuilder.put("client.transport.ping_timeout", fessConfig.getElasticsearchTransportPingTimeout());
276             settingsBuilder.put("client.transport.nodes_sampler_interval", fessConfig.getElasticsearchTransportNodesSamplerInterval());
277             final Settings settings = settingsBuilder.build();
278             final TransportClient transportClient = new PreBuiltTransportClient(settings);
279             for (final TransportAddress address : transportAddressList) {
280                 transportClient.addTransportAddress(address);
281             }
282             client = transportClient;
283         }
284 
285         if (StringUtil.isBlank(transportAddressesValue)) {
286             final StringBuilder buf = new StringBuilder();
287             for (final TransportAddress transportAddress : transportAddressList) {
288                 if (transportAddress instanceof InetSocketTransportAddress) {
289                     if (buf.length() > 0) {
290                         buf.append(',');
291                     }
292                     final InetSocketTransportAddress inetTransportAddress = (InetSocketTransportAddress) transportAddress;
293                     buf.append(inetTransportAddress.address().getHostName());
294                     buf.append(':');
295                     buf.append(inetTransportAddress.address().getPort());
296                 }
297             }
298             if (buf.length() > 0) {
299                 System.setProperty(Constants.FESS_ES_TRANSPORT_ADDRESSES, buf.toString());
300             }
301         }
302 
303         waitForYellowStatus();
304 
305         indexConfigList.forEach(configName -> {
306             final String[] values = configName.split("/");
307             if (values.length == 2) {
308                 final String configIndex = values[0];
309                 final String configType = values[1];
310 
311                 final boolean isFessIndex = configIndex.equals("fess");
312                 final String indexName;
313                 if (isFessIndex) {
314                     final boolean exists = existsIndex(fessConfig.getIndexDocumentUpdateIndex());
315                     if (!exists) {
316                         indexName = generateNewIndexName(configIndex);
317                         createIndex(configIndex, configType, indexName);
318                         createAlias(configIndex, indexName);
319                     } else {
320                         client.admin().cluster().prepareHealth(fessConfig.getIndexDocumentUpdateIndex()).setWaitForYellowStatus().execute()
321                                 .actionGet(fessConfig.getIndexIndicesTimeout());
322                         final GetIndexResponse response =
323                                 client.admin().indices().prepareGetIndex().addIndices(fessConfig.getIndexDocumentUpdateIndex()).execute()
324                                         .actionGet(fessConfig.getIndexIndicesTimeout());
325                         final String[] indices = response.indices();
326                         if (indices.length == 1) {
327                             indexName = indices[0];
328                         } else {
329                             indexName = configIndex;
330                         }
331                     }
332                 } else {
333                     switch (configIndex) {
334                     case ".fess_config":
335                         indexName = fessConfig.getIndexConfigIndex();
336                         break;
337                     case ".fess_user":
338                         indexName = fessConfig.getIndexUserIndex();
339                         break;
340                     case "fess_log":
341                         indexName = fessConfig.getIndexLogIndex();
342                         break;
343                     default:
344                         throw new FessSystemException("Unknown config index: " + configIndex);
345                     }
346                     final boolean exists = existsIndex(indexName);
347                     if (!exists) {
348                         createIndex(configIndex, configType, indexName);
349                         createAlias(configIndex, indexName);
350                     }
351                 }
352 
353                 addMapping(configIndex, configType, indexName);
354             } else {
355                 logger.warn("Invalid index config name: " + configName);
356             }
357         });
358     }
359 
360     public boolean existsIndex(final String indexName) {
361         final FessConfig fessConfig = ComponentUtil.getFessConfig();
362         boolean exists = false;
363         try {
364             final IndicesExistsResponse response =
365                     client.admin().indices().prepareExists(indexName).execute().actionGet(fessConfig.getIndexSearchTimeout());
366             exists = response.isExists();
367         } catch (final Exception e) {
368             // ignore
369         }
370         return exists;
371     }
372 
373     public boolean reindex(final String fromIndex, final String toIndex, final boolean waitForCompletion) {
374         final String source = "{\"source\":{\"index\":\"" + fromIndex + "\"},\"dest\":{\"index\":\"" + toIndex + "\"}}";
375         try (CurlResponse response =
376                 Curl.post(org.codelibs.fess.util.ResourceUtil.getElasticsearchHttpUrl() + "/_reindex")
377                         .header("Content-Type", "application/json").param("wait_for_completion", Boolean.toString(waitForCompletion))
378                         .body(source).execute()) {
379             if (response.getHttpStatusCode() == 200) {
380                 return true;
381             } else {
382                 logger.warn("Failed to reindex from " + fromIndex + " to " + toIndex);
383             }
384         } catch (final IOException e) {
385             logger.warn("Failed to reindex from " + fromIndex + " to " + toIndex, e);
386         }
387         return false;
388     }
389 
390     public boolean createIndex(final String index, final String docType, final String indexName) {
391         final FessConfig fessConfig = ComponentUtil.getFessConfig();
392 
393         waitForConfigSyncStatus();
394         sendConfigFiles(index);
395 
396         final String indexConfigFile = indexConfigPath + "/" + index + ".json";
397         try {
398             String source = FileUtil.readUTF8(indexConfigFile);
399             String dictionaryPath = System.getProperty("fess.dictionary.path", StringUtil.EMPTY);
400             if (StringUtil.isNotBlank(dictionaryPath) && !dictionaryPath.endsWith("/")) {
401                 dictionaryPath = dictionaryPath + "/";
402             }
403             source = source.replaceAll(Pattern.quote("${fess.dictionary.path}"), dictionaryPath);
404             final CreateIndexResponse indexResponse =
405                     client.admin().indices().prepareCreate(indexName).setSource(source, XContentFactory.xContentType(source)).execute()
406                             .actionGet(fessConfig.getIndexIndicesTimeout());
407             if (indexResponse.isAcknowledged()) {
408                 logger.info("Created " + indexName + " index.");
409                 return true;
410             } else if (logger.isDebugEnabled()) {
411                 logger.debug("Failed to create " + indexName + " index.");
412             }
413         } catch (final Exception e) {
414             logger.warn(indexConfigFile + " is not found.", e);
415         }
416 
417         return false;
418     }
419 
420     public void addMapping(final String index, final String docType, final String indexName) {
421         final FessConfig fessConfig = ComponentUtil.getFessConfig();
422 
423         final GetMappingsResponse getMappingsResponse =
424                 client.admin().indices().prepareGetMappings(indexName).execute().actionGet(fessConfig.getIndexIndicesTimeout());
425         final ImmutableOpenMap<String, MappingMetaData> indexMappings = getMappingsResponse.mappings().get(indexName);
426         if (indexMappings == null || !indexMappings.containsKey(docType)) {
427             String source = null;
428             final String mappingFile = indexConfigPath + "/" + index + "/" + docType + ".json";
429             try {
430                 source = FileUtil.readUTF8(mappingFile);
431             } catch (final Exception e) {
432                 logger.warn(mappingFile + " is not found.", e);
433             }
434             try {
435                 final PutMappingResponse putMappingResponse =
436                         client.admin().indices().preparePutMapping(indexName).setType(docType)
437                                 .setSource(source, XContentFactory.xContentType(source)).execute()
438                                 .actionGet(fessConfig.getIndexIndicesTimeout());
439                 if (putMappingResponse.isAcknowledged()) {
440                     logger.info("Created " + indexName + "/" + docType + " mapping.");
441                 } else {
442                     logger.warn("Failed to create " + indexName + "/" + docType + " mapping.");
443                 }
444 
445                 final String dataPath = indexConfigPath + "/" + index + "/" + docType + ".bulk";
446                 if (ResourceUtil.isExist(dataPath)) {
447                     insertBulkData(fessConfig, indexName, docType, dataPath);
448                 }
449             } catch (final Exception e) {
450                 logger.warn("Failed to create " + indexName + "/" + docType + " mapping.", e);
451             }
452         } else if (logger.isDebugEnabled()) {
453             logger.debug(indexName + "/" + docType + " mapping exists.");
454         }
455     }
456 
457     public boolean updateAlias(final String newIndex) {
458         final FessConfig fessConfig = ComponentUtil.getFessConfig();
459         final String updateAlias = fessConfig.getIndexDocumentUpdateIndex();
460         final String searchAlias = fessConfig.getIndexDocumentSearchIndex();
461         final GetIndexResponse response1 =
462                 client.admin().indices().prepareGetIndex().addIndices(updateAlias).execute().actionGet(fessConfig.getIndexIndicesTimeout());
463         final String[] updateIndices = response1.indices();
464         final GetIndexResponse response2 =
465                 client.admin().indices().prepareGetIndex().addIndices(searchAlias).execute().actionGet(fessConfig.getIndexIndicesTimeout());
466         final String[] searchIndices = response2.indices();
467 
468         final IndicesAliasesRequestBuilder builder =
469                 client.admin().indices().prepareAliases().addAlias(newIndex, updateAlias).addAlias(newIndex, searchAlias);
470         for (final String index : updateIndices) {
471             builder.removeAlias(index, updateAlias);
472         }
473         for (final String index : searchIndices) {
474             builder.removeAlias(index, searchAlias);
475         }
476         final IndicesAliasesResponse response = builder.execute().actionGet(fessConfig.getIndexIndicesTimeout());
477         return response.isAcknowledged();
478     }
479 
480     protected void createAlias(final String index, final String createdIndexName) {
481         final FessConfig fessConfig = ComponentUtil.getFessConfig();
482         // alias
483         final String aliasConfigDirPath = indexConfigPath + "/" + index + "/alias";
484         try {
485             final File aliasConfigDir = ResourceUtil.getResourceAsFile(aliasConfigDirPath);
486             if (aliasConfigDir.isDirectory()) {
487                 stream(aliasConfigDir.listFiles((dir, name) -> name.endsWith(".json"))).of(
488                         stream -> stream.forEach(f -> {
489                             final String aliasName = f.getName().replaceFirst(".json$", "");
490                             String source = FileUtil.readUTF8(f);
491                             if (source.trim().equals("{}")) {
492                                 source = null;
493                             }
494                             final IndicesAliasesResponse response =
495                                     client.admin().indices().prepareAliases().addAlias(createdIndexName, aliasName, source).execute()
496                                             .actionGet(fessConfig.getIndexIndicesTimeout());
497                             if (response.isAcknowledged()) {
498                                 logger.info("Created " + aliasName + " alias for " + createdIndexName);
499                             } else if (logger.isDebugEnabled()) {
500                                 logger.debug("Failed to create " + aliasName + " alias for " + createdIndexName);
501                             }
502                         }));
503             }
504         } catch (final ResourceNotFoundRuntimeException e) {
505             // ignore
506         } catch (final Exception e) {
507             logger.warn(aliasConfigDirPath + " is not found.", e);
508         }
509     }
510 
511     protected void sendConfigFiles(final String index) {
512         configListMap.getOrDefault(index, Collections.emptyList()).forEach(
513                 path -> {
514                     String source = null;
515                     final String filePath = indexConfigPath + "/" + index + "/" + path;
516                     try {
517                         source = FileUtil.readUTF8(filePath);
518                         try (CurlResponse response =
519                                 Curl.post(org.codelibs.fess.util.ResourceUtil.getElasticsearchHttpUrl() + "/_configsync/file")
520                                         .header("Content-Type", "application/json").param("path", path).body(source).execute()) {
521                             if (response.getHttpStatusCode() == 200) {
522                                 logger.info("Register " + path + " to " + index);
523                             } else {
524                                 if (response.getContentException() != null) {
525                                     logger.warn("Invalid request for " + path + ".", response.getContentException());
526                                 } else {
527                                     logger.warn("Invalid request for " + path + ". The response is " + response.getContentAsString());
528                                 }
529                             }
530                         }
531                     } catch (final Exception e) {
532                         logger.warn("Failed to register " + filePath, e);
533                     }
534                 });
535         try (CurlResponse response =
536                 Curl.post(org.codelibs.fess.util.ResourceUtil.getElasticsearchHttpUrl() + "/_configsync/flush")
537                         .header("Content-Type", "application/json").execute()) {
538             if (response.getHttpStatusCode() == 200) {
539                 logger.info("Flushed config files.");
540             } else {
541                 logger.warn("Failed to flush config files.");
542             }
543         } catch (final Exception e) {
544             logger.warn("Failed to flush config files.", e);
545         }
546     }
547 
548     protected String generateNewIndexName(final String configIndex) {
549         return configIndex + "." + new SimpleDateFormat("yyyyMMdd").format(new Date());
550     }
551 
552     protected void insertBulkData(final FessConfig fessConfig, final String configIndex, final String configType, final String dataPath) {
553         try {
554             final BulkRequestBuilder builder = client.prepareBulk();
555             final ObjectMapper mapper = new ObjectMapper();
556             Arrays.stream(FileUtil.readUTF8(dataPath).split("\n")).reduce(
557                     (prev, line) -> {
558                         try {
559                             if (StringUtil.isBlank(prev)) {
560                                 final Map<String, Map<String, String>> result =
561                                         mapper.readValue(line, new TypeReference<Map<String, Map<String, String>>>() {
562                                         });
563                                 if (result.keySet().contains("index")) {
564                                     return line;
565                                 } else if (result.keySet().contains("update")) {
566                                     return line;
567                                 } else if (result.keySet().contains("delete")) {
568                                     return StringUtil.EMPTY;
569                                 }
570                             } else {
571                                 final Map<String, Map<String, String>> result =
572                                         mapper.readValue(prev, new TypeReference<Map<String, Map<String, String>>>() {
573                                         });
574                                 if (result.keySet().contains("index")) {
575                                     final IndexRequestBuilder requestBuilder =
576                                             client.prepareIndex(configIndex, configType, result.get("index").get("_id")).setSource(line,
577                                                     XContentFactory.xContentType(line));
578                                     builder.add(requestBuilder);
579                                 }
580                             }
581                         } catch (final Exception e) {
582                             logger.warn("Failed to parse " + dataPath);
583                         }
584                         return StringUtil.EMPTY;
585                     });
586             final BulkResponse response = builder.execute().actionGet(fessConfig.getIndexBulkTimeout());
587             if (response.hasFailures()) {
588                 logger.warn("Failed to register " + dataPath + ": " + response.buildFailureMessage());
589             }
590         } catch (final Exception e) {
591             logger.warn("Failed to create " + configIndex + "/" + configType + " mapping.");
592         }
593     }
594 
595     private void waitForYellowStatus() {
596         final ClusterHealthResponse response =
597                 client.admin().cluster().prepareHealth().setWaitForYellowStatus().execute()
598                         .actionGet(ComponentUtil.getFessConfig().getIndexHealthTimeout());
599         if (logger.isDebugEnabled()) {
600             logger.debug("Elasticsearch Cluster Status: " + response.getStatus());
601         }
602     }
603 
604     private void waitForConfigSyncStatus() {
605         try (CurlResponse response =
606                 Curl.get(org.codelibs.fess.util.ResourceUtil.getElasticsearchHttpUrl() + "/_configsync/wait")
607                         .header("Content-Type", "application/json").param("status", "green").execute()) {
608             if (response.getHttpStatusCode() == 200) {
609                 logger.info("ConfigSync is ready.");
610             } else {
611                 if (response.getContentException() != null) {
612                     throw new FessSystemException("Configsync is not available.", response.getContentException());
613                 } else {
614                     throw new FessSystemException("Configsync is not available.", response.getContentException());
615                 }
616             }
617         } catch (final IOException e) {
618             throw new FessSystemException("Configsync is not available.", e);
619         }
620     }
621 
622     @Override
623     @PreDestroy
624     public void close() {
625         try {
626             client.admin().indices().prepareFlush().setForce(true).execute()
627                     .actionGet(ComponentUtil.getFessConfig().getIndexIndicesTimeout());
628         } catch (final Exception e) {
629             logger.warn("Failed to flush indices.", e);
630         }
631         try {
632             client.close();
633         } catch (final ElasticsearchException e) {
634             logger.warn("Failed to close Client: " + client, e);
635         }
636     }
637 
638     public int deleteByQuery(final String index, final String type, final QueryBuilder queryBuilder) {
639 
640         final FessConfig fessConfig = ComponentUtil.getFessConfig();
641         SearchResponse response =
642                 client.prepareSearch(index).setTypes(type).setScroll(scrollForDelete).setSize(sizeForDelete)
643                         .setFetchSource(new String[] { fessConfig.getIndexFieldId() }, null).setQuery(queryBuilder)
644                         .setPreference(Constants.SEARCH_PREFERENCE_PRIMARY).execute()
645                         .actionGet(fessConfig.getIndexScrollSearchTimeoutTimeout());
646 
647         int count = 0;
648         String scrollId = response.getScrollId();
649         while (scrollId != null) {
650             final SearchHits searchHits = response.getHits();
651             final SearchHit[] hits = searchHits.getHits();
652             if (hits.length == 0) {
653                 scrollId = null;
654                 break;
655             }
656 
657             final BulkRequestBuilder bulkRequest = client.prepareBulk();
658             for (final SearchHit hit : hits) {
659                 bulkRequest.add(client.prepareDelete(index, type, hit.getId()));
660             }
661             count += hits.length;
662             final BulkResponse bulkResponse = bulkRequest.execute().actionGet(fessConfig.getIndexBulkTimeout());
663             if (bulkResponse.hasFailures()) {
664                 throw new IllegalBehaviorStateException(bulkResponse.buildFailureMessage());
665             }
666 
667             response =
668                     client.prepareSearchScroll(scrollId).setScroll(scrollForDelete).execute().actionGet(fessConfig.getIndexBulkTimeout());
669             scrollId = response.getScrollId();
670         }
671         return count;
672     }
673 
674     protected <T> T get(final String index, final String type, final String id, final SearchCondition<GetRequestBuilder> condition,
675             final SearchResult<T, GetRequestBuilder, GetResponse> searchResult) {
676         final long startTime = System.currentTimeMillis();
677 
678         GetResponse response = null;
679         final GetRequestBuilder requestBuilder = client.prepareGet(index, type, id);
680         if (condition.build(requestBuilder)) {
681             response = requestBuilder.execute().actionGet(ComponentUtil.getFessConfig().getIndexSearchTimeout());
682         }
683         final long execTime = System.currentTimeMillis() - startTime;
684 
685         return searchResult.build(requestBuilder, execTime, OptionalEntity.ofNullable(response, () -> {}));
686     }
687 
688     public <T> T search(final String index, final String type, final SearchCondition<SearchRequestBuilder> condition,
689             final SearchResult<T, SearchRequestBuilder, SearchResponse> searchResult) {
690         final long startTime = System.currentTimeMillis();
691 
692         SearchResponse searchResponse = null;
693         final SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index).setTypes(type);
694         if (condition.build(searchRequestBuilder)) {
695 
696             final FessConfig fessConfig = ComponentUtil.getFessConfig();
697             final long queryTimeout = fessConfig.getQueryTimeoutAsInteger().longValue();
698             if (queryTimeout >= 0) {
699                 searchRequestBuilder.setTimeout(TimeValue.timeValueMillis(queryTimeout));
700             }
701 
702             try {
703                 if (logger.isDebugEnabled()) {
704                     logger.debug("Query DSL:\n" + searchRequestBuilder.toString());
705                 }
706                 searchResponse = searchRequestBuilder.execute().actionGet(ComponentUtil.getFessConfig().getIndexSearchTimeout());
707             } catch (final SearchPhaseExecutionException e) {
708                 throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryParseError(UserMessages.GLOBAL_PROPERTY_KEY),
709                         "Invalid query: " + searchRequestBuilder, e);
710             }
711         }
712         final long execTime = System.currentTimeMillis() - startTime;
713 
714         return searchResult.build(searchRequestBuilder, execTime, OptionalEntity.ofNullable(searchResponse, () -> {}));
715     }
716 
717     public OptionalEntity<Map<String, Object>> getDocument(final String index, final String type,
718             final SearchCondition<SearchRequestBuilder> condition) {
719         return getDocument(
720                 index,
721                 type,
722                 condition,
723                 (response, hit) -> {
724                     final FessConfig fessConfig = ComponentUtil.getFessConfig();
725                     final Map<String, Object> source = hit.getSource();
726                     if (source != null) {
727                         final Map<String, Object> docMap = new HashMap<>(source);
728                         docMap.put(fessConfig.getIndexFieldId(), hit.getId());
729                         docMap.put(fessConfig.getIndexFieldVersion(), hit.getVersion());
730                         return docMap;
731                     }
732                     final Map<String, SearchHitField> fields = hit.getFields();
733                     if (fields != null) {
734                         final Map<String, Object> docMap =
735                                 fields.entrySet().stream()
736                                         .collect(Collectors.toMap(e -> e.getKey(), e -> (Object) e.getValue().getValues()));
737                         docMap.put(fessConfig.getIndexFieldId(), hit.getId());
738                         docMap.put(fessConfig.getIndexFieldVersion(), hit.getVersion());
739                         return docMap;
740                     }
741                     return null;
742                 });
743     }
744 
745     protected <T> OptionalEntity<T> getDocument(final String index, final String type,
746             final SearchCondition<SearchRequestBuilder> condition, final EntityCreator<T, SearchResponse, SearchHit> creator) {
747         return search(index, type, searchRequestBuilder -> {
748             searchRequestBuilder.setVersion(true);
749             return condition.build(searchRequestBuilder);
750         }, (queryBuilder, execTime, searchResponse) -> {
751             return searchResponse.map(response -> {
752                 final SearchHit[] hits = response.getHits().getHits();
753                 if (hits.length > 0) {
754                     return creator.build(response, hits[0]);
755                 }
756                 return null;
757             });
758         });
759     }
760 
761     public List<Map<String, Object>> getDocumentList(final String index, final String type,
762             final SearchCondition<SearchRequestBuilder> condition) {
763         return getDocumentList(
764                 index,
765                 type,
766                 condition,
767                 (response, hit) -> {
768                     final FessConfig fessConfig = ComponentUtil.getFessConfig();
769                     final Map<String, Object> source = hit.getSource();
770                     if (source != null) {
771                         final Map<String, Object> docMap = new HashMap<>(source);
772                         docMap.put(fessConfig.getIndexFieldId(), hit.getId());
773                         return docMap;
774                     }
775                     final Map<String, SearchHitField> fields = hit.getFields();
776                     if (fields != null) {
777                         final Map<String, Object> docMap =
778                                 fields.entrySet().stream()
779                                         .collect(Collectors.toMap(e -> e.getKey(), e -> (Object) e.getValue().getValues()));
780                         docMap.put(fessConfig.getIndexFieldId(), hit.getId());
781                         return docMap;
782                     }
783                     return null;
784                 });
785     }
786 
787     protected <T> List<T> getDocumentList(final String index, final String type, final SearchCondition<SearchRequestBuilder> condition,
788             final EntityCreator<T, SearchResponse, SearchHit> creator) {
789         return search(index, type, condition, (searchRequestBuilder, execTime, searchResponse) -> {
790             final List<T> list = new ArrayList<>();
791             searchResponse.ifPresent(response -> {
792                 response.getHits().forEach(hit -> {
793                     list.add(creator.build(response, hit));
794                 });
795             });
796             return list;
797         });
798     }
799 
800     public boolean update(final String index, final String type, final String id, final String field, final Object value) {
801         try {
802             final Result result =
803                     client.prepareUpdate(index, type, id).setDoc(field, value).execute()
804                             .actionGet(ComponentUtil.getFessConfig().getIndexIndexTimeout()).getResult();
805             return result == Result.CREATED || result == Result.UPDATED;
806         } catch (final ElasticsearchException e) {
807             throw new FessEsClientException("Failed to set " + value + " to " + field + " for doc " + id, e);
808         }
809     }
810 
811     public void refresh(final String... indices) {
812         client.admin().indices().prepareRefresh(indices).execute(new ActionListener<RefreshResponse>() {
813             @Override
814             public void onResponse(final RefreshResponse response) {
815                 if (logger.isDebugEnabled()) {
816                     logger.debug("Refreshed " + stream(indices).get(stream -> stream.collect(Collectors.joining(", "))) + ".");
817                 }
818             }
819 
820             @Override
821             public void onFailure(final Exception e) {
822                 logger.error("Failed to refresh " + stream(indices).get(stream -> stream.collect(Collectors.joining(", "))) + ".", e);
823             }
824         });
825 
826     }
827 
828     public void flush(final String... indices) {
829         client.admin().indices().prepareFlush(indices).execute(new ActionListener<FlushResponse>() {
830 
831             @Override
832             public void onResponse(final FlushResponse response) {
833                 if (logger.isDebugEnabled()) {
834                     logger.debug("Flushed " + stream(indices).get(stream -> stream.collect(Collectors.joining(", "))) + ".");
835                 }
836             }
837 
838             @Override
839             public void onFailure(final Exception e) {
840                 logger.error("Failed to flush " + stream(indices).get(stream -> stream.collect(Collectors.joining(", "))) + ".", e);
841             }
842         });
843 
844     }
845 
846     public PingResponse ping() {
847         try {
848             final ClusterHealthResponse response =
849                     client.admin().cluster().prepareHealth().execute().actionGet(ComponentUtil.getFessConfig().getIndexHealthTimeout());
850             return new PingResponse(response);
851         } catch (final ElasticsearchException e) {
852             throw new FessEsClientException("Failed to process a ping request.", e);
853         }
854     }
855 
856     public void addAll(final String index, final String type, final List<Map<String, Object>> docList) {
857         final FessConfig fessConfig = ComponentUtil.getFessConfig();
858         final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
859         for (final Map<String, Object> doc : docList) {
860             final Object id = doc.remove(fessConfig.getIndexFieldId());
861             bulkRequestBuilder.add(client.prepareIndex(index, type, id.toString()).setSource(new DocMap(doc)));
862         }
863         final BulkResponse response = bulkRequestBuilder.execute().actionGet(ComponentUtil.getFessConfig().getIndexBulkTimeout());
864         if (response.hasFailures()) {
865             if (logger.isDebugEnabled()) {
866                 @SuppressWarnings("rawtypes")
867                 final List<DocWriteRequest> requests = bulkRequestBuilder.request().requests();
868                 final BulkItemResponse[] items = response.getItems();
869                 if (requests.size() == items.length) {
870                     for (int i = 0; i < requests.size(); i++) {
871                         final BulkItemResponse resp = items[i];
872                         if (resp.isFailed() && resp.getFailure() != null) {
873                             final DocWriteRequest<?> req = requests.get(i);
874                             final Failure failure = resp.getFailure();
875                             logger.debug("Failed Request: " + req + "\n=>" + failure.getMessage());
876                         }
877                     }
878                 }
879             }
880             throw new FessEsClientException(response.buildFailureMessage());
881         }
882     }
883 
884     public static class SearchConditionBuilder {
885         private final SearchRequestBuilder searchRequestBuilder;
886         private String query;
887         private String[] responseFields;
888         private int offset = Constants.DEFAULT_START_COUNT;
889         private int size = Constants.DEFAULT_PAGE_SIZE;
890         private GeoInfo geoInfo;
891         private FacetInfo facetInfo;
892         private String similarDocHash;
893         private SearchRequestType searchRequestType = SearchRequestType.SEARCH;
894 
895         public static SearchConditionBuilder builder(final SearchRequestBuilder searchRequestBuilder) {
896             return new SearchConditionBuilder(searchRequestBuilder);
897         }
898 
899         SearchConditionBuilder(final SearchRequestBuilder searchRequestBuilder) {
900             this.searchRequestBuilder = searchRequestBuilder;
901         }
902 
903         public SearchConditionBuilder query(final String query) {
904             this.query = query;
905             return this;
906         }
907 
908         public SearchConditionBuilder searchRequestType(final SearchRequestType searchRequestType) {
909             this.searchRequestType = searchRequestType;
910             return this;
911         }
912 
913         public SearchConditionBuilder responseFields(final String[] responseFields) {
914             this.responseFields = responseFields;
915             return this;
916         }
917 
918         public SearchConditionBuilder offset(final int offset) {
919             this.offset = offset;
920             return this;
921         }
922 
923         public SearchConditionBuilder size(final int size) {
924             this.size = size;
925             return this;
926         }
927 
928         public SearchConditionBuilder geoInfo(final GeoInfo geoInfo) {
929             this.geoInfo = geoInfo;
930             return this;
931         }
932 
933         public SearchConditionBuilder similarDocHash(final String similarDocHash) {
934             if (StringUtil.isNotBlank(similarDocHash)) {
935                 this.similarDocHash = similarDocHash;
936             }
937             return this;
938         }
939 
940         public SearchConditionBuilder facetInfo(final FacetInfo facetInfo) {
941             this.facetInfo = facetInfo;
942             return this;
943         }
944 
945         public boolean build() {
946             if (StringUtil.isBlank(query)) {
947                 return false;
948             }
949 
950             final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
951             final FessConfig fessConfig = ComponentUtil.getFessConfig();
952 
953             if (offset > fessConfig.getQueryMaxSearchResultOffsetAsInteger()) {
954                 throw new ResultOffsetExceededException("The number of result size is exceeded.");
955             }
956 
957             final QueryContext queryContext =
958                     queryHelper.build(searchRequestType, query, context -> {
959                         if (SearchRequestType.ADMIN_SEARCH.equals(searchRequestType)) {
960                             context.skipRoleQuery();
961                         } else if (similarDocHash != null) {
962                             final DocumentHelper documentHelper = ComponentUtil.getDocumentHelper();
963                             context.addQuery(boolQuery -> {
964                                 boolQuery.filter(QueryBuilders.termQuery(fessConfig.getIndexFieldContentMinhashBits(),
965                                         documentHelper.decodeSimilarDocHash(similarDocHash)));
966                             });
967                         }
968 
969                         if (geoInfo != null && geoInfo.toQueryBuilder() != null) {
970                             context.addQuery(boolQuery -> {
971                                 boolQuery.filter(geoInfo.toQueryBuilder());
972                             });
973                         }
974                     });
975 
976             searchRequestBuilder.setFrom(offset).setSize(size);
977 
978             if (responseFields != null) {
979                 searchRequestBuilder.setFetchSource(responseFields, null);
980             }
981 
982             // sort
983             queryContext.sortBuilders().forEach(sortBuilder -> searchRequestBuilder.addSort(sortBuilder));
984 
985             // highlighting
986             final HighlightBuilder highlightBuilder = new HighlightBuilder();
987             queryHelper.highlightedFields(stream -> stream.forEach(hf -> highlightBuilder.field(new HighlightBuilder.Field(hf)
988                     .highlighterType(fessConfig.getQueryHighlightType()).fragmentSize(fessConfig.getQueryHighlightFragmentSizeAsInteger())
989                     .numOfFragments(fessConfig.getQueryHighlightNumberOfFragmentsAsInteger()))));
990             searchRequestBuilder.highlighter(highlightBuilder);
991 
992             // facets
993             if (facetInfo != null) {
994                 stream(facetInfo.field).of(
995                         stream -> stream.forEach(f -> {
996                             if (queryHelper.isFacetField(f)) {
997                                 final String encodedField = BaseEncoding.base64().encode(f.getBytes(StandardCharsets.UTF_8));
998                                 final TermsAggregationBuilder termsBuilder =
999                                         AggregationBuilders.terms(Constants.FACET_FIELD_PREFIX + encodedField).field(f);
1000                                 if ("term".equals(facetInfo.sort)) {
1001                                     termsBuilder.order(Order.term(true));
1002                                 } else if ("count".equals(facetInfo.sort)) {
1003                                     termsBuilder.order(Order.count(true));
1004                                 }
1005                                 if (facetInfo.size != null) {
1006                                     termsBuilder.size(facetInfo.size);
1007                                 }
1008                                 if (facetInfo.minDocCount != null) {
1009                                     termsBuilder.minDocCount(facetInfo.minDocCount);
1010                                 }
1011                                 if (facetInfo.missing != null) {
1012                                     termsBuilder.missing(facetInfo.missing);
1013                                 }
1014                                 searchRequestBuilder.addAggregation(termsBuilder);
1015                             } else {
1016                                 throw new SearchQueryException("Invalid facet field: " + f);
1017                             }
1018                         }));
1019                 stream(facetInfo.query).of(
1020                         stream -> stream.forEach(fq -> {
1021                             final QueryContext facetContext = new QueryContext(fq, false);
1022                             queryHelper.buildBaseQuery(facetContext, c -> {});
1023                             final String encodedFacetQuery = BaseEncoding.base64().encode(fq.getBytes(StandardCharsets.UTF_8));
1024                             final FilterAggregationBuilder filterBuilder =
1025                                     AggregationBuilders.filter(Constants.FACET_QUERY_PREFIX + encodedFacetQuery,
1026                                             facetContext.getQueryBuilder());
1027                             searchRequestBuilder.addAggregation(filterBuilder);
1028                         }));
1029             }
1030 
1031             if (!SearchRequestType.ADMIN_SEARCH.equals(searchRequestType) && fessConfig.isResultCollapsed() && similarDocHash == null) {
1032                 searchRequestBuilder.setCollapse(getCollapseBuilder(fessConfig));
1033             }
1034 
1035             searchRequestBuilder.setQuery(queryContext.getQueryBuilder());
1036             return true;
1037         }
1038 
1039         protected CollapseBuilder getCollapseBuilder(final FessConfig fessConfig) {
1040             final InnerHitBuilder innerHitBuilder =
1041                     new InnerHitBuilder().setName(fessConfig.getQueryCollapseInnerHitsName()).setSize(
1042                             fessConfig.getQueryCollapseInnerHitsSizeAsInteger());
1043             fessConfig.getQueryCollapseInnerHitsSortBuilders().ifPresent(
1044                     builders -> stream(builders).of(stream -> stream.forEach(innerHitBuilder::addSort)));
1045             return new CollapseBuilder(fessConfig.getIndexFieldContentMinhashBits()).setMaxConcurrentGroupRequests(
1046                     fessConfig.getQueryCollapseMaxConcurrentGroupResultsAsInteger()).setInnerHits(innerHitBuilder);
1047         }
1048     }
1049 
1050     public boolean store(final String index, final String type, final Object obj) {
1051         final FessConfig fessConfig = ComponentUtil.getFessConfig();
1052         @SuppressWarnings("unchecked")
1053         final Map<String, Object> source = obj instanceof Map ? (Map<String, Object>) obj : BeanUtil.copyBeanToNewMap(obj);
1054         final String id = (String) source.remove(fessConfig.getIndexFieldId());
1055         final Number version = (Number) source.remove(fessConfig.getIndexFieldVersion());
1056         IndexResponse response;
1057         try {
1058             if (id == null) {
1059                 // TODO throw Exception in next release
1060                 // create
1061                 response =
1062                         client.prepareIndex(index, type).setSource(new DocMap(source)).setRefreshPolicy(RefreshPolicy.IMMEDIATE)
1063                                 .setOpType(OpType.CREATE).execute().actionGet(fessConfig.getIndexIndexTimeout());
1064             } else {
1065                 // create or update
1066                 final IndexRequestBuilder builder =
1067                         client.prepareIndex(index, type, id).setSource(new DocMap(source)).setRefreshPolicy(RefreshPolicy.IMMEDIATE)
1068                                 .setOpType(OpType.INDEX);
1069                 if (version != null && version.longValue() > 0) {
1070                     builder.setVersion(version.longValue());
1071                 }
1072                 response = builder.execute().actionGet(fessConfig.getIndexIndexTimeout());
1073             }
1074             final Result result = response.getResult();
1075             return result == Result.CREATED || result == Result.UPDATED;
1076         } catch (final ElasticsearchException e) {
1077             throw new FessEsClientException("Failed to store: " + obj, e);
1078         }
1079     }
1080 
1081     public boolean delete(final String index, final String type, final String id, final long version) {
1082         try {
1083             final DeleteRequestBuilder builder = client.prepareDelete(index, type, id).setRefreshPolicy(RefreshPolicy.IMMEDIATE);
1084             if (version > 0) {
1085                 builder.setVersion(version);
1086             }
1087             final DeleteResponse response = builder.execute().actionGet(ComponentUtil.getFessConfig().getIndexDeleteTimeout());
1088             return response.getResult() == Result.DELETED;
1089         } catch (final ElasticsearchException e) {
1090             throw new FessEsClientException("Failed to delete: " + index + "/" + type + "/" + id + "/" + version, e);
1091         }
1092     }
1093 
1094     public void setIndexConfigPath(final String indexConfigPath) {
1095         this.indexConfigPath = indexConfigPath;
1096     }
1097 
1098     public interface SearchCondition<B> {
1099         boolean build(B requestBuilder);
1100     }
1101 
1102     public interface SearchResult<T, B, R> {
1103         T build(B requestBuilder, long execTime, OptionalEntity<R> response);
1104     }
1105 
1106     public interface EntityCreator<T, R, H> {
1107         T build(R response, H hit);
1108     }
1109 
1110     //
1111     // Elasticsearch Client
1112     //
1113 
1114     @Override
1115     public ThreadPool threadPool() {
1116         return client.threadPool();
1117     }
1118 
1119     @Override
1120     public AdminClient admin() {
1121         return client.admin();
1122     }
1123 
1124     @Override
1125     public ActionFuture<IndexResponse> index(final IndexRequest request) {
1126         return client.index(request);
1127     }
1128 
1129     @Override
1130     public void index(final IndexRequest request, final ActionListener<IndexResponse> listener) {
1131         client.index(request, listener);
1132     }
1133 
1134     @Override
1135     public IndexRequestBuilder prepareIndex() {
1136         return client.prepareIndex();
1137     }
1138 
1139     @Override
1140     public ActionFuture<UpdateResponse> update(final UpdateRequest request) {
1141         return client.update(request);
1142     }
1143 
1144     @Override
1145     public void update(final UpdateRequest request, final ActionListener<UpdateResponse> listener) {
1146         client.update(request, listener);
1147     }
1148 
1149     @Override
1150     public UpdateRequestBuilder prepareUpdate() {
1151         return client.prepareUpdate();
1152     }
1153 
1154     @Override
1155     public UpdateRequestBuilder prepareUpdate(final String index, final String type, final String id) {
1156         return client.prepareUpdate(index, type, id);
1157     }
1158 
1159     @Override
1160     public IndexRequestBuilder prepareIndex(final String index, final String type) {
1161         return client.prepareIndex(index, type);
1162     }
1163 
1164     @Override
1165     public IndexRequestBuilder prepareIndex(final String index, final String type, final String id) {
1166         return client.prepareIndex(index, type, id);
1167     }
1168 
1169     @Override
1170     public ActionFuture<DeleteResponse> delete(final DeleteRequest request) {
1171         return client.delete(request);
1172     }
1173 
1174     @Override
1175     public void delete(final DeleteRequest request, final ActionListener<DeleteResponse> listener) {
1176         client.delete(request, listener);
1177     }
1178 
1179     @Override
1180     public DeleteRequestBuilder prepareDelete() {
1181         return client.prepareDelete();
1182     }
1183 
1184     @Override
1185     public DeleteRequestBuilder prepareDelete(final String index, final String type, final String id) {
1186         return client.prepareDelete(index, type, id);
1187     }
1188 
1189     @Override
1190     public ActionFuture<BulkResponse> bulk(final BulkRequest request) {
1191         return client.bulk(request);
1192     }
1193 
1194     @Override
1195     public void bulk(final BulkRequest request, final ActionListener<BulkResponse> listener) {
1196         client.bulk(request, listener);
1197     }
1198 
1199     @Override
1200     public BulkRequestBuilder prepareBulk() {
1201         return client.prepareBulk();
1202     }
1203 
1204     @Override
1205     public ActionFuture<GetResponse> get(final GetRequest request) {
1206         return client.get(request);
1207     }
1208 
1209     @Override
1210     public void get(final GetRequest request, final ActionListener<GetResponse> listener) {
1211         client.get(request, listener);
1212     }
1213 
1214     @Override
1215     public GetRequestBuilder prepareGet() {
1216         return client.prepareGet();
1217     }
1218 
1219     @Override
1220     public GetRequestBuilder prepareGet(final String index, final String type, final String id) {
1221         return client.prepareGet(index, type, id);
1222     }
1223 
1224     @Override
1225     public ActionFuture<MultiGetResponse> multiGet(final MultiGetRequest request) {
1226         return client.multiGet(request);
1227     }
1228 
1229     @Override
1230     public void multiGet(final MultiGetRequest request, final ActionListener<MultiGetResponse> listener) {
1231         client.multiGet(request, listener);
1232     }
1233 
1234     @Override
1235     public MultiGetRequestBuilder prepareMultiGet() {
1236         return client.prepareMultiGet();
1237     }
1238 
1239     @Override
1240     public ActionFuture<SearchResponse> search(final SearchRequest request) {
1241         return client.search(request);
1242     }
1243 
1244     @Override
1245     public void search(final SearchRequest request, final ActionListener<SearchResponse> listener) {
1246         client.search(request, listener);
1247     }
1248 
1249     @Override
1250     public SearchRequestBuilder prepareSearch(final String... indices) {
1251         return client.prepareSearch(indices);
1252     }
1253 
1254     @Override
1255     public ActionFuture<SearchResponse> searchScroll(final SearchScrollRequest request) {
1256         return client.searchScroll(request);
1257     }
1258 
1259     @Override
1260     public void searchScroll(final SearchScrollRequest request, final ActionListener<SearchResponse> listener) {
1261         client.searchScroll(request, listener);
1262     }
1263 
1264     @Override
1265     public SearchScrollRequestBuilder prepareSearchScroll(final String scrollId) {
1266         return client.prepareSearchScroll(scrollId);
1267     }
1268 
1269     @Override
1270     public ActionFuture<MultiSearchResponse> multiSearch(final MultiSearchRequest request) {
1271         return client.multiSearch(request);
1272     }
1273 
1274     @Override
1275     public void multiSearch(final MultiSearchRequest request, final ActionListener<MultiSearchResponse> listener) {
1276         client.multiSearch(request, listener);
1277     }
1278 
1279     @Override
1280     public MultiSearchRequestBuilder prepareMultiSearch() {
1281         return client.prepareMultiSearch();
1282     }
1283 
1284     @Override
1285     public ExplainRequestBuilder prepareExplain(final String index, final String type, final String id) {
1286         return client.prepareExplain(index, type, id);
1287     }
1288 
1289     @Override
1290     public ActionFuture<ExplainResponse> explain(final ExplainRequest request) {
1291         return client.explain(request);
1292     }
1293 
1294     @Override
1295     public void explain(final ExplainRequest request, final ActionListener<ExplainResponse> listener) {
1296         client.explain(request, listener);
1297     }
1298 
1299     @Override
1300     public ClearScrollRequestBuilder prepareClearScroll() {
1301         return client.prepareClearScroll();
1302     }
1303 
1304     @Override
1305     public ActionFuture<ClearScrollResponse> clearScroll(final ClearScrollRequest request) {
1306         return client.clearScroll(request);
1307     }
1308 
1309     @Override
1310     public void clearScroll(final ClearScrollRequest request, final ActionListener<ClearScrollResponse> listener) {
1311         client.clearScroll(request, listener);
1312     }
1313 
1314     @Override
1315     public FieldStatsRequestBuilder prepareFieldStats() {
1316         return client.prepareFieldStats();
1317     }
1318 
1319     @Override
1320     public ActionFuture<FieldStatsResponse> fieldStats(final FieldStatsRequest request) {
1321         return client.fieldStats(request);
1322     }
1323 
1324     @Override
1325     public void fieldStats(final FieldStatsRequest request, final ActionListener<FieldStatsResponse> listener) {
1326         client.fieldStats(request, listener);
1327     }
1328 
1329     @Override
1330     public Settings settings() {
1331         return client.settings();
1332     }
1333 
1334     @Override
1335     public ActionFuture<TermVectorsResponse> termVectors(final TermVectorsRequest request) {
1336         return client.termVectors(request);
1337     }
1338 
1339     @Override
1340     public void termVectors(final TermVectorsRequest request, final ActionListener<TermVectorsResponse> listener) {
1341         client.termVectors(request, listener);
1342     }
1343 
1344     @Override
1345     public TermVectorsRequestBuilder prepareTermVectors() {
1346         return client.prepareTermVectors();
1347     }
1348 
1349     @Override
1350     public TermVectorsRequestBuilder prepareTermVectors(final String index, final String type, final String id) {
1351         return client.prepareTermVectors(index, type, id);
1352     }
1353 
1354     @Override
1355     @Deprecated
1356     public ActionFuture<TermVectorsResponse> termVector(final TermVectorsRequest request) {
1357         return client.termVector(request);
1358     }
1359 
1360     @Override
1361     @Deprecated
1362     public void termVector(final TermVectorsRequest request, final ActionListener<TermVectorsResponse> listener) {
1363         client.termVector(request, listener);
1364     }
1365 
1366     @Override
1367     @Deprecated
1368     public TermVectorsRequestBuilder prepareTermVector() {
1369         return client.prepareTermVector();
1370     }
1371 
1372     @Override
1373     @Deprecated
1374     public TermVectorsRequestBuilder prepareTermVector(final String index, final String type, final String id) {
1375         return client.prepareTermVector(index, type, id);
1376     }
1377 
1378     @Override
1379     public ActionFuture<MultiTermVectorsResponse> multiTermVectors(final MultiTermVectorsRequest request) {
1380         return client.multiTermVectors(request);
1381     }
1382 
1383     @Override
1384     public void multiTermVectors(final MultiTermVectorsRequest request, final ActionListener<MultiTermVectorsResponse> listener) {
1385         client.multiTermVectors(request, listener);
1386     }
1387 
1388     @Override
1389     public MultiTermVectorsRequestBuilder prepareMultiTermVectors() {
1390         return client.prepareMultiTermVectors();
1391     }
1392 
1393     public void setSizeForDelete(final int sizeForDelete) {
1394         this.sizeForDelete = sizeForDelete;
1395     }
1396 
1397     public void setScrollForDelete(final String scrollForDelete) {
1398         this.scrollForDelete = scrollForDelete;
1399     }
1400 
1401     @Override
1402     public Client filterWithHeader(final Map<String, String> headers) {
1403         return client.filterWithHeader(headers);
1404     }
1405 
1406     @Override
1407     public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> ActionFuture<Response> execute(
1408             final Action<Request, Response, RequestBuilder> action, final Request request) {
1409         return client.execute(action, request);
1410     }
1411 
1412     @Override
1413     public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void execute(
1414             final Action<Request, Response, RequestBuilder> action, final Request request, final ActionListener<Response> listener) {
1415         client.execute(action, request, listener);
1416     }
1417 
1418     @Override
1419     public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> RequestBuilder prepareExecute(
1420             final Action<Request, Response, RequestBuilder> action) {
1421         return client.prepareExecute(action);
1422     }
1423 
1424     @Override
1425     public FieldCapabilitiesRequestBuilder prepareFieldCaps() {
1426         return client.prepareFieldCaps();
1427     }
1428 
1429     @Override
1430     public ActionFuture<FieldCapabilitiesResponse> fieldCaps(final FieldCapabilitiesRequest request) {
1431         return client.fieldCaps(request);
1432     }
1433 
1434     @Override
1435     public void fieldCaps(final FieldCapabilitiesRequest request, final ActionListener<FieldCapabilitiesResponse> listener) {
1436         client.fieldCaps(request, listener);
1437     }
1438 
1439 }