From 673bf3b1e64de0aeca4c4f0d4177395e3b7640ae Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Tue, 8 Apr 2025 15:47:12 +0200 Subject: [PATCH 01/14] Restructure reference docs --- docs/docset.yml | 2 +- docs/local/README.md | 1 - docs/reference/_snippets/doc-tests-blurb.md | 1 + docs/reference/api-conventions.md | 28 ------- .../{ => api-conventions}/blocking-async.md | 10 ++- .../{ => api-conventions}/building-objects.md | 3 +- .../exception-conventions.md | 0 docs/reference/api-conventions/index.md | 28 +++++++ .../{ => api-conventions}/lists-maps.md | 3 +- .../{ => api-conventions}/loading-json.md | 3 +- .../{ => api-conventions}/method-naming.md | 0 .../object-lifecycles.md | 0 .../package-structure.md | 2 +- .../{ => api-conventions}/variant-types.md | 6 +- docs/reference/getting-started.md | 16 ++-- docs/reference/index.md | 10 +-- docs/reference/java-rest-low-config.md | 16 ---- docs/reference/javadoc-source-code.md | 2 +- docs/reference/{_license.md => license.md} | 0 docs/reference/migrate-hlrc.md | 74 ----------------- docs/reference/setup.md | 17 ---- docs/reference/{ => setup}/connecting.md | 9 ++- docs/reference/setup/index.md | 16 ++++ docs/reference/{ => setup}/installation.md | 0 docs/reference/{ => setup}/opentelemetry.md | 0 docs/reference/toc.yml | 79 +++++++++++-------- docs/reference/transport/index.md | 1 + .../config/basic_authentication.md} | 0 .../config/encrypted_communication.md} | 0 .../transport/rest-client/config/index.md | 16 ++++ .../rest-client/config/node_selector.md} | 0 .../rest-client/config/number_of_threads.md} | 0 .../config/other_authentication_methods.md} | 0 .../rest-client/config/others.md} | 0 .../rest-client/config/timeouts.md} | 0 .../rest-client/index.md} | 4 +- .../rest-client/sniffer/index.md} | 1 - .../rest-client/sniffer/javadoc.md} | 2 +- .../rest-client/sniffer/maven_repository.md} | 4 +- .../rest-client/sniffer/usage.md} | 2 +- .../rest-client/usage/dependencies.md} | 0 .../rest-client/usage/index.md} | 0 .../rest-client/usage/initialization.md} | 0 .../rest-client/usage/javadoc.md} | 2 +- .../rest-client/usage/logging.md} | 0 .../rest-client/usage/maven.md} | 6 +- .../rest-client/usage/requests.md} | 0 .../rest-client/usage/responses.md} | 0 .../rest-client/usage/shading.md} | 0 docs/reference/{ => usage}/aggregations.md | 5 +- docs/reference/{ => usage}/esql.md | 0 .../index.md} | 14 ++-- docs/reference/{ => usage}/indexing-bulk.md | 9 ++- docs/reference/{ => usage}/indexing.md | 7 +- docs/reference/{ => usage}/reading.md | 3 +- docs/reference/{ => usage}/searching.md | 11 +-- 56 files changed, 185 insertions(+), 228 deletions(-) delete mode 100644 docs/local/README.md create mode 100644 docs/reference/_snippets/doc-tests-blurb.md delete mode 100644 docs/reference/api-conventions.md rename docs/reference/{ => api-conventions}/blocking-async.md (85%) rename docs/reference/{ => api-conventions}/building-objects.md (95%) rename docs/reference/{ => api-conventions}/exception-conventions.md (100%) create mode 100644 docs/reference/api-conventions/index.md rename docs/reference/{ => api-conventions}/lists-maps.md (93%) rename docs/reference/{ => api-conventions}/loading-json.md (96%) rename docs/reference/{ => api-conventions}/method-naming.md (100%) rename docs/reference/{ => api-conventions}/object-lifecycles.md (100%) rename docs/reference/{ => api-conventions}/package-structure.md (95%) rename docs/reference/{ => api-conventions}/variant-types.md (96%) delete mode 100644 docs/reference/java-rest-low-config.md rename docs/reference/{_license.md => license.md} (100%) delete mode 100644 docs/reference/migrate-hlrc.md delete mode 100644 docs/reference/setup.md rename docs/reference/{ => setup}/connecting.md (93%) create mode 100644 docs/reference/setup/index.md rename docs/reference/{ => setup}/installation.md (100%) rename docs/reference/{ => setup}/opentelemetry.md (100%) create mode 100644 docs/reference/transport/index.md rename docs/reference/{_basic_authentication.md => transport/rest-client/config/basic_authentication.md} (100%) rename docs/reference/{_encrypted_communication.md => transport/rest-client/config/encrypted_communication.md} (100%) create mode 100644 docs/reference/transport/rest-client/config/index.md rename docs/reference/{_node_selector.md => transport/rest-client/config/node_selector.md} (100%) rename docs/reference/{_number_of_threads.md => transport/rest-client/config/number_of_threads.md} (100%) rename docs/reference/{_other_authentication_methods.md => transport/rest-client/config/other_authentication_methods.md} (100%) rename docs/reference/{_others.md => transport/rest-client/config/others.md} (100%) rename docs/reference/{_timeouts.md => transport/rest-client/config/timeouts.md} (100%) rename docs/reference/{java-low-level-rest-client.md => transport/rest-client/index.md} (83%) rename docs/reference/{sniffer.md => transport/rest-client/sniffer/index.md} (90%) rename docs/reference/{java-rest-sniffer-javadoc.md => transport/rest-client/sniffer/javadoc.md} (61%) rename docs/reference/{_maven_repository.md => transport/rest-client/sniffer/maven_repository.md} (96%) rename docs/reference/{_usage.md => transport/rest-client/sniffer/usage.md} (93%) rename docs/reference/{java-rest-low-usage-dependencies.md => transport/rest-client/usage/dependencies.md} (100%) rename docs/reference/{java-rest-low-usage.md => transport/rest-client/usage/index.md} (100%) rename docs/reference/{java-rest-low-usage-initialization.md => transport/rest-client/usage/initialization.md} (100%) rename docs/reference/{java-rest-low-javadoc.md => transport/rest-client/usage/javadoc.md} (63%) rename docs/reference/{java-rest-low-usage-logging.md => transport/rest-client/usage/logging.md} (100%) rename docs/reference/{java-rest-low-usage-maven.md => transport/rest-client/usage/maven.md} (95%) rename docs/reference/{java-rest-low-usage-requests.md => transport/rest-client/usage/requests.md} (100%) rename docs/reference/{java-rest-low-usage-responses.md => transport/rest-client/usage/responses.md} (100%) rename docs/reference/{java-rest-low-usage-shading.md => transport/rest-client/usage/shading.md} (100%) rename docs/reference/{ => usage}/aggregations.md (88%) rename docs/reference/{ => usage}/esql.md (100%) rename docs/reference/{using-java-api-client.md => usage/index.md} (69%) rename docs/reference/{ => usage}/indexing-bulk.md (92%) rename docs/reference/{ => usage}/indexing.md (92%) rename docs/reference/{ => usage}/reading.md (91%) rename docs/reference/{ => usage}/searching.md (88%) diff --git a/docs/docset.yml b/docs/docset.yml index b4b03b47d..19cad0c5e 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -2,7 +2,6 @@ project: 'Java API Client' exclude: - external-resources.md - design/* - - local/README.md cross_links: - docs-content - elasticsearch @@ -13,3 +12,4 @@ subs: serverless-full: "Elastic Cloud Serverless" es3: "Elasticsearch Serverless" es: "Elasticsearch" + version: "9.0.0" diff --git a/docs/local/README.md b/docs/local/README.md deleted file mode 100644 index a68e64521..000000000 --- a/docs/local/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory contains stubs that allow building the Java client docs in isolation following the instructions in "[Building documentation for a local repo](https://github.com/elastic/docs#building-documentation)". diff --git a/docs/reference/_snippets/doc-tests-blurb.md b/docs/reference/_snippets/doc-tests-blurb.md new file mode 100644 index 000000000..4bbd549e2 --- /dev/null +++ b/docs/reference/_snippets/doc-tests-blurb.md @@ -0,0 +1 @@ +The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/main/java-client/src/test/java/co/elastic/clients/documentation). diff --git a/docs/reference/api-conventions.md b/docs/reference/api-conventions.md deleted file mode 100644 index 629a150cc..000000000 --- a/docs/reference/api-conventions.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -mapped_pages: - - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/api-conventions.html ---- - -# API conventions [api-conventions] - -The Java API Client uses a very consistent code structure, using modern code patterns that make complex requests easier to write and complex responses easier to process. The sections below explain these in details. - -* [Package structure and namespace clients](/reference/package-structure.md) -* [Method naming conventions](/reference/method-naming.md) -* [Blocking and asynchronous clients](/reference/blocking-async.md) -* [Building API objects](/reference/building-objects.md) -* [Lists and maps](/reference/lists-maps.md) -* [Variant types](/reference/variant-types.md) -* [Object life cycles and thread safety](/reference/object-lifecycles.md) -* [Creating API objects from JSON data](/reference/loading-json.md) -* [Exceptions](/reference/exception-conventions.md) - - - - - - - - - - diff --git a/docs/reference/blocking-async.md b/docs/reference/api-conventions/blocking-async.md similarity index 85% rename from docs/reference/blocking-async.md rename to docs/reference/api-conventions/blocking-async.md index d7456d3b3..0d9d09b38 100644 --- a/docs/reference/blocking-async.md +++ b/docs/reference/api-conventions/blocking-async.md @@ -9,6 +9,13 @@ API clients come in two flavors: blocking and asynchronous. All methods on async Both flavors can be used at the same time depending on your needs, sharing the same transport object: + + ```java ElasticsearchTransport transport = ... @@ -36,5 +43,6 @@ asyncClient Although we won’t go in deeper details on asynchronous programming in Java, remember to handle failures of asynchronous tasks. It’s easy to overlook them and have errors go unnoticed. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/building-objects.md b/docs/reference/api-conventions/building-objects.md similarity index 95% rename from docs/reference/building-objects.md rename to docs/reference/api-conventions/building-objects.md index 8222060f9..01180a7ed 100644 --- a/docs/reference/building-objects.md +++ b/docs/reference/api-conventions/building-objects.md @@ -99,5 +99,6 @@ SearchResponse results = client 1. Search results will be mapped to `SomeApplicationData` instances to be readily available to the application. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/exception-conventions.md b/docs/reference/api-conventions/exception-conventions.md similarity index 100% rename from docs/reference/exception-conventions.md rename to docs/reference/api-conventions/exception-conventions.md diff --git a/docs/reference/api-conventions/index.md b/docs/reference/api-conventions/index.md new file mode 100644 index 000000000..e36e5978c --- /dev/null +++ b/docs/reference/api-conventions/index.md @@ -0,0 +1,28 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/api-conventions.html +--- + +# API conventions [api-conventions] + +The Java API Client uses a very consistent code structure, using modern code patterns that make complex requests easier to write and complex responses easier to process. The sections below explain these in details. + +* [Package structure and namespace clients](package-structure.md) +* [Method naming conventions](method-naming.md) +* [Blocking and asynchronous clients](blocking-async.md) +* [Building API objects](building-objects.md) +* [Lists and maps](lists-maps.md) +* [Variant types](variant-types.md) +* [Object life cycles and thread safety](object-lifecycles.md) +* [Creating API objects from JSON data](loading-json.md) +* [Exceptions](exception-conventions.md) + + + + + + + + + + diff --git a/docs/reference/lists-maps.md b/docs/reference/api-conventions/lists-maps.md similarity index 93% rename from docs/reference/lists-maps.md rename to docs/reference/api-conventions/lists-maps.md index b31308fd3..8e79e3816 100644 --- a/docs/reference/lists-maps.md +++ b/docs/reference/api-conventions/lists-maps.md @@ -73,5 +73,6 @@ assertEquals(0, stats.failures().size()); assertFalse(ApiTypeHelper.isDefined(stats.failures())); ``` -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/loading-json.md b/docs/reference/api-conventions/loading-json.md similarity index 96% rename from docs/reference/loading-json.md rename to docs/reference/api-conventions/loading-json.md index 4a4227528..06131ba01 100644 --- a/docs/reference/loading-json.md +++ b/docs/reference/api-conventions/loading-json.md @@ -167,5 +167,6 @@ Map aggs = client Notice that order matters when the JSON snippets have some common properties: just as when setting property values programmatically, the last value that is set for a property overwrites the previous one. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/method-naming.md b/docs/reference/api-conventions/method-naming.md similarity index 100% rename from docs/reference/method-naming.md rename to docs/reference/api-conventions/method-naming.md diff --git a/docs/reference/object-lifecycles.md b/docs/reference/api-conventions/object-lifecycles.md similarity index 100% rename from docs/reference/object-lifecycles.md rename to docs/reference/api-conventions/object-lifecycles.md diff --git a/docs/reference/package-structure.md b/docs/reference/api-conventions/package-structure.md similarity index 95% rename from docs/reference/package-structure.md rename to docs/reference/api-conventions/package-structure.md index 48bed9ba9..744a2d9e5 100644 --- a/docs/reference/package-structure.md +++ b/docs/reference/api-conventions/package-structure.md @@ -11,7 +11,7 @@ The Java API Client follows this structure: feature groups are called “namespa Each of the namespace clients can be accessed from the top level {{es}} client. The only exceptions are the “search” and “document” APIs which are located in the `core` subpackage and can be accessed on the main {{es}} client object. -The snippet below shows how to use the indices namespace client to create an index (the lambda syntax is explained in [Building API objects](/reference/building-objects.md)): +The snippet below shows how to use the indices namespace client to create an index (the lambda syntax is explained in [Building API objects](building-objects.md)): ```java // Create the "products" index diff --git a/docs/reference/variant-types.md b/docs/reference/api-conventions/variant-types.md similarity index 96% rename from docs/reference/variant-types.md rename to docs/reference/api-conventions/variant-types.md index 9540ac463..53dda3896 100644 --- a/docs/reference/variant-types.md +++ b/docs/reference/api-conventions/variant-types.md @@ -114,7 +114,7 @@ for (JsonValue item : buckets) { } ``` -1. Use `Void` if you’re only interested in aggregation results, not search hits (see also [Aggregations](/reference/aggregations.md)). +1. Use `Void` if you’re only interested in aggregation results, not search hits (see also [Aggregations](/reference/usage/aggregations.md)). 2. Get the `neighbors` aggregation result as custom JSON result. 3. Traverse the JSON tree to extract the result data. @@ -172,5 +172,5 @@ public static class Bucket { } ``` -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). - +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/getting-started.md b/docs/reference/getting-started.md index e365d854d..f97d6de02 100644 --- a/docs/reference/getting-started.md +++ b/docs/reference/getting-started.md @@ -19,9 +19,9 @@ This page guides you through the installation process of the Java client, shows #### Installation in a Gradle project by using Jackson [_installation_in_a_gradle_project_by_using_jackson] -```groovy +```groovy subs=true dependencies { - implementation 'co.elastic.clients:elasticsearch-java:9.0.0-beta1' + implementation 'co.elastic.clients:elasticsearch-java:{{version}}' } ``` @@ -30,21 +30,21 @@ dependencies { In the `pom.xml` of your project, add the following repository definition and dependencies: -```xml +```xml subs=true co.elastic.clients elasticsearch-java - 9.0.0-beta1 + {{version}} ``` -Refer to the [Installation](/reference/installation.md) page to learn more. +Refer to the [Installation](setup/installation.md) page to learn more. ### Connecting [_connecting] @@ -89,12 +89,12 @@ You can generate an API key on the **Management** page under Security. :alt: Create API key ::: -For other connection options, refer to the [Connecting](/reference/connecting.md) section. +For other connection options, refer to the [Connecting](setup/connecting.md) section. ### Operations [_operations] -Time to use Elasticsearch! This section walks you through the basic, and most important, operations of Elasticsearch. For more operations and more advanced examples, refer to the [*Using the Java API Client*](/reference/using-java-api-client.md) page. +Time to use Elasticsearch! This section walks you through the basic, and most important, operations of Elasticsearch. For more operations and more advanced examples, refer to the [*Using the Java API Client*](usage/index.md) page. #### Creating an index [_creating_an_index] @@ -208,4 +208,4 @@ The [examples](https://github.com/elastic/elasticsearch-java/tree/main/examples) ## Further reading [_further_reading] -* Learn more about the [*API conventions*](/reference/api-conventions.md) of the Java client. +* Learn more about the [*API conventions*](api-conventions/index.md) of the Java client. diff --git a/docs/reference/index.md b/docs/reference/index.md index 8f9fdc876..591fad7f2 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -15,7 +15,7 @@ This is the documentation for the official Java API Client for {{es}}. The clien * Blocking and asynchronous versions of all APIs. * Use of fluent builders and functional patterns to allow writing concise yet readable code when creating complex nested structures. * Seamless integration of application classes by using an object mapper such as Jackson or any JSON-B implementation. -* Delegates protocol handling to an http client such as the [Java Low Level REST Client](/reference/java-low-level-rest-client.md) that takes care of all transport-level concerns: HTTP connection pooling, retries, node discovery, and so on. +* Delegates protocol handling to an http client such as the [Java Low Level REST Client](transport/rest-client/) that takes care of all transport-level concerns: HTTP connection pooling, retries, node discovery, and so on. ## Elasticsearch server compatibility policy [_elasticsearch_server_compatibility_policy] @@ -23,8 +23,8 @@ This is the documentation for the official Java API Client for {{es}}. The clien The {{es}} Java client is forward compatible; meaning that the client supports communicating with greater or equal minor versions of {{es}} without breaking. It does not mean that the client automatically supports new features of newer {{es}} versions; it is only possible after a release of a new client version. For example, a 8.12 client version won’t automatically support the new features of the 8.13 version of {{es}}, the 8.13 client version is required for that. {{es}} language clients are only backwards compatible with default distributions and without guarantees made. | Elasticsearch Version | Elasticsearch-Java Branch | Supported | -| --- | --- | --- | -| main | main | | -| 8.x | 8.x | 8.x | -| 7.x | 7.x | 7.17 | +|-----------------------|---------------------------|-----------| +| main | main | | +| 9.x | 9.x | 9.x | +| 8.x | 8.x | 8.17 | diff --git a/docs/reference/java-rest-low-config.md b/docs/reference/java-rest-low-config.md deleted file mode 100644 index 52574079d..000000000 --- a/docs/reference/java-rest-low-config.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -mapped_pages: - - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-config.html ---- - -# Common configuration [java-rest-low-config] - -As explained in [Initialization](/reference/java-rest-low-usage-initialization.md), the `RestClientBuilder` supports providing both a `RequestConfigCallback` and an `HttpClientConfigCallback` which allow for any customization that the Apache Async Http Client exposes. Those callbacks make it possible to modify some specific behaviour of the client without overriding every other default configuration that the `RestClient` is initialized with. This section describes some common scenarios that require additional configuration for the low-level Java REST Client. - - - - - - - - diff --git a/docs/reference/javadoc-source-code.md b/docs/reference/javadoc-source-code.md index abcdd4183..808267fb0 100644 --- a/docs/reference/javadoc-source-code.md +++ b/docs/reference/javadoc-source-code.md @@ -5,7 +5,7 @@ mapped_pages: # Javadoc and source code [java-client-javadoc] -The javadoc for the Java API Client can be found at [https://snapshots.elastic.co/javadoc/co/elastic/clients/elasticsearch-java/9.0.0-beta1-SNAPSHOT/index.html](https://snapshots.elastic.co/javadoc/co/elastic/clients/elasticsearch-java/9.0.0-beta1-SNAPSHOT/index.md). +The javadoc for the Java API Client can be found at [https://snapshots.elastic.co/javadoc/co/elastic/clients/elasticsearch-java/{{version}}/](https://snapshots.elastic.co/javadoc/co/elastic/clients/elasticsearch-java/{{version}}/). The source code is at [https://github.com/elastic/elasticsearch-java/](https://github.com/elastic/elasticsearch-java/) and is licensed under the Apache 2.0 License. diff --git a/docs/reference/_license.md b/docs/reference/license.md similarity index 100% rename from docs/reference/_license.md rename to docs/reference/license.md diff --git a/docs/reference/migrate-hlrc.md b/docs/reference/migrate-hlrc.md deleted file mode 100644 index 66ab070e7..000000000 --- a/docs/reference/migrate-hlrc.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -mapped_pages: - - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/migrate-hlrc.html ---- - -# Migrating from the High Level Rest Client [migrate-hlrc] - -The {{es}} Java API Client is an entirely new client library that has no relation to the older High Level Rest Client (HLRC). This was a deliberate choice to provide a library that is independent from the {{es}} server code and that provides a very consistent and easier to use API for all {{es}} features. - -Migrating from the HLRC therefore requires some code rewrite in your application. This transition can however happen progressively as the two client libraries can coexist in a single application with no operational overhead. - - -## Compatibility mode: using a 7.17 client with {{es}} 8.x [_compatibility_mode_using_a_7_17_client_with_es_8_x] - -The HLRC version `7.17` can be used with {{es}} version `8.x` by enabling HLRC’s compatibility mode (see code sample below). In this mode HLRC sends additional headers that instruct {{es}} `8.x` to behave like a `7.x` server. - -The Java API Client doesn’t need this setting as compatibility mode is always enabled. - -You can use the HLRC version `7.x` with the Java API Client version `8.x`: - -```groovy -dependencies { - implementation 'co.elastic.clients:elasticsearch-java:9.0.0-beta1' - implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.17.4' - // other dependencies <1> -} -``` - -1. See [Installation](/reference/installation.md) - - - -## Using the same http client with the HLRC and the Java API Client [_using_the_same_http_client_with_the_hlrc_and_the_java_api_client] - -To avoid any operational overhead during the transition phase where an application would use both the HLRC and the new Java API Client, both clients can share the same Low Level Rest Client, which is the network layer that manages all connections, round-robin strategies, node sniffing, and so on. - -The code below shows how to initialize both clients with the same HTTP client: - -```java -// Create the low-level client -RestClient httpClient = RestClient.builder( - new HttpHost("localhost", 9200) -).build(); - -// Create the HLRC -RestHighLevelClient hlrc = new RestHighLevelClientBuilder(httpClient) - .setApiCompatibilityMode(true) <1> - .build(); - -// Create the Java API Client with the same low level client -ElasticsearchTransport transport = new RestClientTransport( - httpClient, - new JacksonJsonpMapper() -); - -ElasticsearchClient esClient = new ElasticsearchClient(transport); - -// hlrc and esClient share the same httpClient -``` - -1. Enables compatibility mode that allows HLRC `7.17` to work with {{es}} `8.x`. - - - -## Transition strategies [_transition_strategies] - -There are different ways you can start transitioning away from the HLRC in your application code. - -For example: - -* keep the existing code as-is and use the new Java API Client for new features in your application, then later migrate the existing code, -* rewrite the parts of your application where the new Java API Client is much easier to use than that of the HLRC, like everything related to search, -* rewrite those parts where you need to map application objects to/from JSON, by leveraging the tight integration of the new Java API Client with JSON object mappers. - diff --git a/docs/reference/setup.md b/docs/reference/setup.md deleted file mode 100644 index 55bb77419..000000000 --- a/docs/reference/setup.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -mapped_pages: - - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/_setup.html ---- - -# Setup [_setup] - -* [Installation](/reference/installation.md) -* [Connecting](/reference/connecting.md) -* [Migrating from the High Level Rest Client](/reference/migrate-hlrc.md) -* [Using OpenTelemetry](/reference/opentelemetry.md) -* [Java Low Level REST Client](/reference/java-low-level-rest-client.md) - - - - - diff --git a/docs/reference/connecting.md b/docs/reference/setup/connecting.md similarity index 93% rename from docs/reference/connecting.md rename to docs/reference/setup/connecting.md index 0974a6bd7..4477ff5c5 100644 --- a/docs/reference/connecting.md +++ b/docs/reference/setup/connecting.md @@ -39,14 +39,14 @@ ElasticsearchClient esClient = new ElasticsearchClient(transport); esClient.close(); ``` -Authentication is managed by the [Java Low Level REST Client](/reference/java-low-level-rest-client.md). For further details on configuring authentication, refer to [its documentation](/reference/_basic_authentication.md). +Authentication is managed by the [Java Low Level REST Client](/reference/transport/rest-client/index.md). For further details on configuring authentication, refer to [its documentation](/reference/transport/rest-client/config/basic_authentication.md). ## Your first request [_your_first_request] The code snippet below searches all items from a “product” index whose name matches “bicycle” and return them as instances of a `Product` application class. -It illustrates the use of fluent functional builders to write search queries as concise DSL-like code. This pattern is explained in more detail in [*API conventions*](/reference/api-conventions.md). +It illustrates the use of fluent functional builders to write search queries as concise DSL-like code. This pattern is explained in more detail in [*API conventions*](/reference/api-conventions/index.md). ```java SearchResponse search = esClient.search(s -> s @@ -66,7 +66,7 @@ for (Hit hit: search.hits().hits()) { ## Using a secure connection [using-a-secure-connection] -The [Java Low Level REST Client](/reference/java-low-level-rest-client.md) documentation explains how to set up encrypted communications in detail. +The [Java Low Level REST Client](/reference/transport/rest-client/index.md) documentation explains how to set up encrypted communications in detail. In self-managed installations, Elasticsearch will start with security features like authentication and TLS enabled. To connect to the Elasticsearch cluster you’ll need to configure the Java API Client to use HTTPS with the generated CA certificate in order to make requests successfully. @@ -183,4 +183,5 @@ esClient.close(); 4. Configure the http client with the SSL and authentication configurations. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/setup/index.md b/docs/reference/setup/index.md new file mode 100644 index 000000000..e0509009c --- /dev/null +++ b/docs/reference/setup/index.md @@ -0,0 +1,16 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/_setup.html +--- + +# Setup [_setup] + +* [Installation](installation.md) +* [Connecting](connecting.md) +* [Using OpenTelemetry](opentelemetry.md) +* [Java Low Level REST Client](/reference/transport/rest-client/index.md) + + + + + diff --git a/docs/reference/installation.md b/docs/reference/setup/installation.md similarity index 100% rename from docs/reference/installation.md rename to docs/reference/setup/installation.md diff --git a/docs/reference/opentelemetry.md b/docs/reference/setup/opentelemetry.md similarity index 100% rename from docs/reference/opentelemetry.md rename to docs/reference/setup/opentelemetry.md diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml index 282c0f690..db894666d 100644 --- a/docs/reference/toc.yml +++ b/docs/reference/toc.yml @@ -1,14 +1,17 @@ toc: - file: index.md - file: getting-started.md - - file: setup.md + + - folder: setup children: + - file: index.md - file: installation.md - file: connecting.md - - file: migrate-hlrc.md - file: opentelemetry.md - - file: api-conventions.md + + - folder: api-conventions children: + - file: index.md - file: package-structure.md - file: method-naming.md - file: blocking-async.md @@ -18,42 +21,54 @@ toc: - file: object-lifecycles.md - file: loading-json.md - file: exception-conventions.md - - file: using-java-api-client.md + + - folder: usage children: - - file: esql.md + - file: index.md - file: indexing.md - file: indexing-bulk.md - file: reading.md - file: searching.md - file: aggregations.md - - file: javadoc-source-code.md - - file: external-resources.md - - file: java-low-level-rest-client.md + - file: esql.md + + - folder: transport children: - - file: java-rest-low-usage.md + - file: index.md + + - folder: rest-client children: - - file: java-rest-low-javadoc.md - - file: java-rest-low-usage-maven.md - - file: java-rest-low-usage-dependencies.md - - file: java-rest-low-usage-shading.md - - file: java-rest-low-usage-initialization.md - - file: java-rest-low-usage-requests.md - - file: java-rest-low-usage-responses.md - - file: java-rest-low-usage-logging.md - - file: java-rest-low-config.md - children: - - file: _timeouts.md - - file: _number_of_threads.md - - file: _basic_authentication.md - - file: _other_authentication_methods.md - - file: _encrypted_communication.md - - file: _others.md - - file: _node_selector.md - - file: sniffer.md - children: - - file: java-rest-sniffer-javadoc.md - - file: _maven_repository.md - - file: _usage.md + - file: index.md + - folder: usage + children: + - file: index.md + - file: javadoc.md + - file: maven.md + - file: dependencies.md + - file: shading.md + - file: initialization.md + - file: requests.md + - file: responses.md + - file: logging.md + - folder: config + children: + - file: index.md + - file: timeouts.md + - file: number_of_threads.md + - file: basic_authentication.md + - file: other_authentication_methods.md + - file: encrypted_communication.md + - file: others.md + - file: node_selector.md + - folder: sniffer + children: + - file: index.md + - file: javadoc.md + - file: maven_repository.md + - file: usage.md + + - file: javadoc-source-code.md + - file: external-resources.md - file: breaking-changes-policy.md - file: release-highlights.md - - file: _license.md \ No newline at end of file + - file: license.md diff --git a/docs/reference/transport/index.md b/docs/reference/transport/index.md new file mode 100644 index 000000000..291983004 --- /dev/null +++ b/docs/reference/transport/index.md @@ -0,0 +1 @@ +# HTTP transport diff --git a/docs/reference/_basic_authentication.md b/docs/reference/transport/rest-client/config/basic_authentication.md similarity index 100% rename from docs/reference/_basic_authentication.md rename to docs/reference/transport/rest-client/config/basic_authentication.md diff --git a/docs/reference/_encrypted_communication.md b/docs/reference/transport/rest-client/config/encrypted_communication.md similarity index 100% rename from docs/reference/_encrypted_communication.md rename to docs/reference/transport/rest-client/config/encrypted_communication.md diff --git a/docs/reference/transport/rest-client/config/index.md b/docs/reference/transport/rest-client/config/index.md new file mode 100644 index 000000000..afadab117 --- /dev/null +++ b/docs/reference/transport/rest-client/config/index.md @@ -0,0 +1,16 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-config.html +--- + +# Common configuration [java-rest-low-config] + +As explained in [Initialization](../usage/initialization.md), the `RestClientBuilder` supports providing both a `RequestConfigCallback` and an `HttpClientConfigCallback` which allow for any customization that the Apache Async Http Client exposes. Those callbacks make it possible to modify some specific behaviour of the client without overriding every other default configuration that the `RestClient` is initialized with. This section describes some common scenarios that require additional configuration for the low-level Java REST Client. + + + + + + + + diff --git a/docs/reference/_node_selector.md b/docs/reference/transport/rest-client/config/node_selector.md similarity index 100% rename from docs/reference/_node_selector.md rename to docs/reference/transport/rest-client/config/node_selector.md diff --git a/docs/reference/_number_of_threads.md b/docs/reference/transport/rest-client/config/number_of_threads.md similarity index 100% rename from docs/reference/_number_of_threads.md rename to docs/reference/transport/rest-client/config/number_of_threads.md diff --git a/docs/reference/_other_authentication_methods.md b/docs/reference/transport/rest-client/config/other_authentication_methods.md similarity index 100% rename from docs/reference/_other_authentication_methods.md rename to docs/reference/transport/rest-client/config/other_authentication_methods.md diff --git a/docs/reference/_others.md b/docs/reference/transport/rest-client/config/others.md similarity index 100% rename from docs/reference/_others.md rename to docs/reference/transport/rest-client/config/others.md diff --git a/docs/reference/_timeouts.md b/docs/reference/transport/rest-client/config/timeouts.md similarity index 100% rename from docs/reference/_timeouts.md rename to docs/reference/transport/rest-client/config/timeouts.md diff --git a/docs/reference/java-low-level-rest-client.md b/docs/reference/transport/rest-client/index.md similarity index 83% rename from docs/reference/java-low-level-rest-client.md rename to docs/reference/transport/rest-client/index.md index 05e7eeea2..f2144c0db 100644 --- a/docs/reference/java-low-level-rest-client.md +++ b/docs/reference/transport/rest-client/index.md @@ -3,7 +3,7 @@ mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low.html --- -# Java Low Level REST Client [java-rest-low] +# Legacy REST Client [java-rest-low] The low-level client’s features include: @@ -13,5 +13,5 @@ The low-level client’s features include: * failed connection penalization (whether a failed node is retried depends on how many consecutive times it failed; the more failed attempts the longer the client will wait before trying that same node again) * persistent connections * trace logging of requests and responses -* optional automatic [discovery of cluster nodes](/reference/sniffer.md) +* optional automatic [discovery of cluster nodes](sniffer/index.md) diff --git a/docs/reference/sniffer.md b/docs/reference/transport/rest-client/sniffer/index.md similarity index 90% rename from docs/reference/sniffer.md rename to docs/reference/transport/rest-client/sniffer/index.md index cbb67a7f4..883ab087a 100644 --- a/docs/reference/sniffer.md +++ b/docs/reference/transport/rest-client/sniffer/index.md @@ -7,7 +7,6 @@ mapped_pages: Minimal library that allows to automatically discover nodes from a running Elasticsearch cluster and set them to an existing `RestClient` instance. It retrieves by default the nodes that belong to the cluster using the Nodes Info api and uses jackson to parse the obtained json response. -Compatible with Elasticsearch 2.x and onwards. diff --git a/docs/reference/java-rest-sniffer-javadoc.md b/docs/reference/transport/rest-client/sniffer/javadoc.md similarity index 61% rename from docs/reference/java-rest-sniffer-javadoc.md rename to docs/reference/transport/rest-client/sniffer/javadoc.md index a24c16f1c..6088ce0a0 100644 --- a/docs/reference/java-rest-sniffer-javadoc.md +++ b/docs/reference/transport/rest-client/sniffer/javadoc.md @@ -5,5 +5,5 @@ mapped_pages: # Javadoc [java-rest-sniffer-javadoc] -The javadoc for the REST client sniffer can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/9.0.0-beta1-SNAPSHOT/index.html](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/9.0.0-beta1-SNAPSHOT/index.md). +The javadoc for the REST client sniffer can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/{{version}}/index.html](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/{{version}}/index.md). diff --git a/docs/reference/_maven_repository.md b/docs/reference/transport/rest-client/sniffer/maven_repository.md similarity index 96% rename from docs/reference/_maven_repository.md rename to docs/reference/transport/rest-client/sniffer/maven_repository.md index 2f5324972..3b327b1ea 100644 --- a/docs/reference/_maven_repository.md +++ b/docs/reference/transport/rest-client/sniffer/maven_repository.md @@ -13,11 +13,11 @@ If you are looking for a SNAPSHOT version, the Elastic Maven Snapshot repository Here is how you can configure the dependency using maven as a dependency manager. Add the following to your `pom.xml` file: -```xml +```xml subs=true org.elasticsearch.client elasticsearch-rest-client-sniffer - 9.0.0-beta1 + {{version}} ``` diff --git a/docs/reference/_usage.md b/docs/reference/transport/rest-client/sniffer/usage.md similarity index 93% rename from docs/reference/_usage.md rename to docs/reference/transport/rest-client/sniffer/usage.md index d38511e87..540f73a02 100644 --- a/docs/reference/_usage.md +++ b/docs/reference/transport/rest-client/sniffer/usage.md @@ -5,7 +5,7 @@ mapped_pages: # Usage [_usage] -Once a `RestClient` instance has been created as shown in [Initialization](/reference/java-rest-low-usage-initialization.md), a `Sniffer` can be associated to it. The `Sniffer` will make use of the provided `RestClient` to periodically (every 5 minutes by default) fetch the list of current nodes from the cluster and update them by calling `RestClient#setNodes`. +Once a `RestClient` instance has been created as shown in [Initialization](../usage/initialization.md), a `Sniffer` can be associated to it. The `Sniffer` will make use of the provided `RestClient` to periodically (every 5 minutes by default) fetch the list of current nodes from the cluster and update them by calling `RestClient#setNodes`. ```java RestClient restClient = RestClient.builder( diff --git a/docs/reference/java-rest-low-usage-dependencies.md b/docs/reference/transport/rest-client/usage/dependencies.md similarity index 100% rename from docs/reference/java-rest-low-usage-dependencies.md rename to docs/reference/transport/rest-client/usage/dependencies.md diff --git a/docs/reference/java-rest-low-usage.md b/docs/reference/transport/rest-client/usage/index.md similarity index 100% rename from docs/reference/java-rest-low-usage.md rename to docs/reference/transport/rest-client/usage/index.md diff --git a/docs/reference/java-rest-low-usage-initialization.md b/docs/reference/transport/rest-client/usage/initialization.md similarity index 100% rename from docs/reference/java-rest-low-usage-initialization.md rename to docs/reference/transport/rest-client/usage/initialization.md diff --git a/docs/reference/java-rest-low-javadoc.md b/docs/reference/transport/rest-client/usage/javadoc.md similarity index 63% rename from docs/reference/java-rest-low-javadoc.md rename to docs/reference/transport/rest-client/usage/javadoc.md index e80307d97..cb9299544 100644 --- a/docs/reference/java-rest-low-javadoc.md +++ b/docs/reference/transport/rest-client/usage/javadoc.md @@ -5,5 +5,5 @@ mapped_pages: # Javadoc [java-rest-low-javadoc] -The javadoc for the low level REST client can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/9.0.0-beta1-SNAPSHOT/index.html](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/9.0.0-beta1-SNAPSHOT/index.md). +The javadoc for the low level REST client can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/{{version}}/](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/{{version}}/). diff --git a/docs/reference/java-rest-low-usage-logging.md b/docs/reference/transport/rest-client/usage/logging.md similarity index 100% rename from docs/reference/java-rest-low-usage-logging.md rename to docs/reference/transport/rest-client/usage/logging.md diff --git a/docs/reference/java-rest-low-usage-maven.md b/docs/reference/transport/rest-client/usage/maven.md similarity index 95% rename from docs/reference/java-rest-low-usage-maven.md rename to docs/reference/transport/rest-client/usage/maven.md index e6c97b5df..af4248fac 100644 --- a/docs/reference/java-rest-low-usage-maven.md +++ b/docs/reference/transport/rest-client/usage/maven.md @@ -15,11 +15,11 @@ If you are looking for a SNAPSHOT version, the Elastic Maven Snapshot repository Here is how you can configure the dependency using maven as a dependency manager. Add the following to your `pom.xml` file: -```xml +```xml subs=true org.elasticsearch.client elasticsearch-rest-client - 9.0.0-beta1 + {{version}} ``` @@ -30,7 +30,7 @@ Here is how you can configure the dependency using gradle as a dependency manage ```groovy dependencies { - compile 'org.elasticsearch.client:elasticsearch-rest-client:9.0.0-beta1' + compile 'org.elasticsearch.client:elasticsearch-rest-client:{{version}}' } ``` diff --git a/docs/reference/java-rest-low-usage-requests.md b/docs/reference/transport/rest-client/usage/requests.md similarity index 100% rename from docs/reference/java-rest-low-usage-requests.md rename to docs/reference/transport/rest-client/usage/requests.md diff --git a/docs/reference/java-rest-low-usage-responses.md b/docs/reference/transport/rest-client/usage/responses.md similarity index 100% rename from docs/reference/java-rest-low-usage-responses.md rename to docs/reference/transport/rest-client/usage/responses.md diff --git a/docs/reference/java-rest-low-usage-shading.md b/docs/reference/transport/rest-client/usage/shading.md similarity index 100% rename from docs/reference/java-rest-low-usage-shading.md rename to docs/reference/transport/rest-client/usage/shading.md diff --git a/docs/reference/aggregations.md b/docs/reference/usage/aggregations.md similarity index 88% rename from docs/reference/aggregations.md rename to docs/reference/usage/aggregations.md index a711e35b4..2a464ceb1 100644 --- a/docs/reference/aggregations.md +++ b/docs/reference/usage/aggregations.md @@ -15,7 +15,7 @@ See the [{{es}} documentation](elasticsearch://reference/aggregations/index.md) ## A simple aggregation [_a_simple_aggregation] -In the example below we run an aggregation that creates a price histogram from a product index, for the products whose name match a user-provided text. To achieve this, we use a search request that has a query (explained in [Searching for documents](/reference/searching.md)) and an aggregation definition. +In the example below we run an aggregation that creates a price histogram from a product index, for the products whose name match a user-provided text. To achieve this, we use a search request that has a query (explained in [Searching for documents](searching.md)) and an aggregation definition. This example is an analytics-type aggregation where we do not want to use the matching documents. A general pattern for search requests used for analytics is to set the result `size` to zero and the target class for search results to `Void`. @@ -69,5 +69,6 @@ for (HistogramBucket bucket: buckets) { 3. Buckets can be expressed as arrays or maps. This casts down to the array variant (the default). -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/esql.md b/docs/reference/usage/esql.md similarity index 100% rename from docs/reference/esql.md rename to docs/reference/usage/esql.md diff --git a/docs/reference/using-java-api-client.md b/docs/reference/usage/index.md similarity index 69% rename from docs/reference/using-java-api-client.md rename to docs/reference/usage/index.md index 6eac73b84..567d33ae5 100644 --- a/docs/reference/using-java-api-client.md +++ b/docs/reference/usage/index.md @@ -7,16 +7,16 @@ mapped_pages: The sections below provide tutorials on the most frequently used and some less obvious features of {{es}}. -For a full reference, see the [Elasticsearch documentation](docs-content://get-started/index.md) and in particular the [REST APIs](elasticsearch://reference/elasticsearch/rest-apis/index.md) section. The Java API Client follows closely the JSON structures described there, using the [Java API conventions](/reference/api-conventions.md). +For a full reference, see the [Elasticsearch documentation](docs-content://get-started/index.md) and in particular the [REST APIs](elasticsearch://reference/elasticsearch/rest-apis/index.md) section. The Java API Client follows closely the JSON structures described there, using the [Java API conventions](/reference/api-conventions/index.md). If you’re new to Elasticsearch, make sure also to read [Elasticsearch’s quick start](docs-content://solutions/search/get-started.md) that provides a good introduction. -* [ES|QL in the Java client](/reference/esql.md) -* [Indexing single documents](/reference/indexing.md) -* [Bulk: indexing multiple documents](/reference/indexing-bulk.md) -* [Reading documents by id](/reference/reading.md) -* [Searching for documents](/reference/searching.md) -* [Aggregations](/reference/aggregations.md) +* [ES|QL in the Java client](/reference/usage/esql.md) +* [Indexing single documents](/reference/usage/indexing.md) +* [Bulk: indexing multiple documents](/reference/usage/indexing-bulk.md) +* [Reading documents by id](/reference/usage/reading.md) +* [Searching for documents](/reference/usage/searching.md) +* [Aggregations](/reference/usage/aggregations.md) ::::{note} This is still a work in progress, more sections will be added in the near future. diff --git a/docs/reference/indexing-bulk.md b/docs/reference/usage/indexing-bulk.md similarity index 92% rename from docs/reference/indexing-bulk.md rename to docs/reference/usage/indexing-bulk.md index 0b3eb882d..6848512e9 100644 --- a/docs/reference/indexing-bulk.md +++ b/docs/reference/usage/indexing-bulk.md @@ -22,7 +22,7 @@ See the [{{es}} API documentation](https://www.elastic.co/docs/api/doc/elasticse ## Indexing application objects [_indexing_application_objects] -A `BulkRequest` contains a collection of operations, each operation being a [type with several variants](/reference/variant-types.md). To create this request, it is convenient to use a builder object for the main request, and the fluent DSL for each operation. +A `BulkRequest` contains a collection of operations, each operation being a [type with several variants](/reference/api-conventions/variant-types.md). To create this request, it is convenient to use a builder object for the main request, and the fluent DSL for each operation. The example below shows how to index a list or application objects. @@ -54,9 +54,9 @@ if (result.errors()) { } ``` -1. Adds an operation (remember that [list properties are additive](/reference/lists-maps.md)). `op` is is a builder for `BulkOperation` which is a [variant type](/reference/variant-types.md). This type has `index`, `create`, `update` and `delete` variants. +1. Adds an operation (remember that [list properties are additive](/reference/api-conventions/lists-maps.md)). `op` is is a builder for `BulkOperation` which is a [variant type](/reference/api-conventions/variant-types.md). This type has `index`, `create`, `update` and `delete` variants. 2. Selects the `index` operation variant, `idx` is a builder for `IndexOperation`. -3. Sets the properties for the index operation, similar to [single document indexing](/reference/indexing.md): index name, identifier and document. +3. Sets the properties for the index operation, similar to [single document indexing](indexing.md): index name, identifier and document. @@ -195,5 +195,6 @@ The bulk ingest also exposes statistic information that allows monitoring the in * number of bulk requests sent, * number of bulk requests that were blocked because the maximum number of concurrent requests was reached. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/indexing.md b/docs/reference/usage/indexing.md similarity index 92% rename from docs/reference/indexing.md rename to docs/reference/usage/indexing.md index f95d91fac..469696cff 100644 --- a/docs/reference/indexing.md +++ b/docs/reference/usage/indexing.md @@ -68,7 +68,7 @@ logger.info("Indexed with version " + response.version()); ## Using the asynchronous client [_using_the_asynchronous_client] -The examples above used the synchronous {{es}} client. All {{es}} APIs are also available in the asynchronous client, using the same request and response types. See also [Blocking and asynchronous clients](/reference/blocking-async.md) for additional details. +The examples above used the synchronous {{es}} client. All {{es}} APIs are also available in the asynchronous client, using the same request and response types. See also [Blocking and asynchronous clients](/reference/api-conventions/blocking-async.md) for additional details. ```java ElasticsearchAsyncClient esAsyncClient = new ElasticsearchAsyncClient(transport); @@ -93,7 +93,7 @@ esAsyncClient.index(i -> i When the data you want to index comes from external sources, having to create domain objects may be cumbersome or outright impossible with semi-structured data. -You can index data from an arbitrary source using `withJson()`. Using this method will read the source and use it for the index request’s `document` property. See [Creating API objects from JSON data](/reference/loading-json.md) for additional details. +You can index data from an arbitrary source using `withJson()`. Using this method will read the source and use it for the index request’s `document` property. See [Creating API objects from JSON data](/reference/api-conventions/loading-json.md) for additional details. ```java Reader input = new StringReader( @@ -110,5 +110,6 @@ IndexResponse response = esClient.index(request); logger.info("Indexed with version " + response.version()); ``` -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/reading.md b/docs/reference/usage/reading.md similarity index 91% rename from docs/reference/reading.md rename to docs/reference/usage/reading.md index e6f27305b..39aefc7b3 100644 --- a/docs/reference/reading.md +++ b/docs/reference/usage/reading.md @@ -67,5 +67,6 @@ if (response.found()) { 1. The target class is a raw JSON object. -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: diff --git a/docs/reference/searching.md b/docs/reference/usage/searching.md similarity index 88% rename from docs/reference/searching.md rename to docs/reference/usage/searching.md index f2eb732d1..0affa3c57 100644 --- a/docs/reference/searching.md +++ b/docs/reference/usage/searching.md @@ -54,13 +54,13 @@ for (Hit hit: hits) { ``` 1. Name of the index we want to search. -2. The query part of the search request (a search request can also have other components like [aggregations](/reference/aggregations.md)). +2. The query part of the search request (a search request can also have other components like [aggregations](aggregations.md)). 3. Choose a query variant among the many available. We choose here the match query (full text search). 4. Configure the match query: we search for a term in the `name` field. -5. The target class for the matching documents. We use `Product` here, just like in [get request](/reference/reading.md) examples. +5. The target class for the matching documents. We use `Product` here, just like in [get request](reading.md) examples. -Similarly to [get](/reference/reading.md) operations, you can fetch documents matching your query as raw JSON by using a corresponding target class instead of `Product`, like JSON-P’s `JsonValue` or Jackson’s `ObjectNode`. +Similarly to [get](reading.md) operations, you can fetch documents matching your query as raw JSON by using a corresponding target class instead of `Product`, like JSON-P’s `JsonValue` or Jackson’s `ObjectNode`. ## Nested search queries [_nested_search_queries] @@ -104,7 +104,7 @@ for (Hit hit: hits) { ``` 1. We’re creating the queries for individual criteria separately. -2. A `MatchQuery` is a query *variant* that we have to turn into the `Query` *union type*. See [variant types](/reference/variant-types.md) for additional details. +2. A `MatchQuery` is a query *variant* that we have to turn into the `Query` *union type*. See [variant types](/reference/api-conventions/variant-types.md) for additional details. 3. {{es}} range query accepts a large range of value types. We create here a JSON representation of the maximum price. 4. The search query is a boolean query that combines the text search and max price queries. 5. Both queries are added as `must` as we want results to match all criteria. @@ -154,5 +154,6 @@ for (Hit hit: hits) { For more in-depth information, see the [{{es}} search template documentation](docs-content://solutions/search/search-templates.md). -The source code for the examples above can be found in the [Java API Client tests](https://github.com/elastic/elasticsearch-java/tree/master/java-client/src/test/java/co/elastic/clients/documentation). +:::{include} /reference/_snippets/doc-tests-blurb.md +::: From 8486ed6b2a57a5c9f9f5e5843f458a04bb7ae879 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Tue, 8 Apr 2025 19:20:04 +0200 Subject: [PATCH 02/14] Add code snippets inclusion tool --- .../api-conventions/blocking-async.md | 4 +- .../api-conventions/building-objects.md | 34 ++- docs/reference/api-conventions/lists-maps.md | 15 +- .../reference/api-conventions/loading-json.md | 50 +++- .../api-conventions/variant-types.md | 73 ++++- docs/reference/getting-started.md | 84 ++++-- docs/reference/setup/connecting.md | 93 +++--- docs/reference/setup/installation.md | 14 +- docs/reference/setup/opentelemetry.md | 24 +- docs/reference/toc.yml | 3 + docs/reference/usage/aggregations.md | 31 +- docs/reference/usage/indexing-bulk.md | 50 +++- docs/reference/usage/indexing.md | 36 ++- docs/reference/usage/reading.md | 20 +- docs/reference/usage/searching.md | 57 +++- .../api_conventions/LoadingJsonTest.java | 24 +- .../documentation/usage/IndexingBulkTest.java | 6 +- settings.gradle.kts | 1 + tools/build.gradle.kts | 51 ++++ .../clients/tools/docs/IncludeExpander.java | 264 ++++++++++++++++++ 20 files changed, 768 insertions(+), 166 deletions(-) create mode 100644 tools/build.gradle.kts create mode 100644 tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java diff --git a/docs/reference/api-conventions/blocking-async.md b/docs/reference/api-conventions/blocking-async.md index 0d9d09b38..5e0786246 100644 --- a/docs/reference/api-conventions/blocking-async.md +++ b/docs/reference/api-conventions/blocking-async.md @@ -15,10 +15,9 @@ ElasticsearchTransport transport = ... :::{include} {doc-tests-src}/api_conventions/ApiConventionsTest.java[blocking-and-async] ``` --> - +% :::include::start -- do not remove ```java ElasticsearchTransport transport = ... - // Synchronous blocking client ElasticsearchClient client = new ElasticsearchClient(transport); @@ -40,6 +39,7 @@ asyncClient } }); ``` +% :::include::end -- do not remove Although we won’t go in deeper details on asynchronous programming in Java, remember to handle failures of asynchronous tasks. It’s easy to overlook them and have errors go unnoticed. diff --git a/docs/reference/api-conventions/building-objects.md b/docs/reference/api-conventions/building-objects.md index 01180a7ed..677144b3f 100644 --- a/docs/reference/api-conventions/building-objects.md +++ b/docs/reference/api-conventions/building-objects.md @@ -10,6 +10,13 @@ mapped_pages: All data types in the Java API Client are immutable. Object creation uses the [builder pattern](https://www.informit.com/articles/article.aspx?p=1216151&seqNum=2) that was popularized in **Effective Java** in 2008. + +% :::include::start -- do not remove ```java ElasticsearchClient client = ... CreateIndexResponse createResponse = client.indices().create( @@ -21,6 +28,7 @@ CreateIndexResponse createResponse = client.indices().create( .build() ); ``` +% :::include::end -- do not remove Note that a builder should not be reused after its `build()` method has been called. @@ -29,6 +37,13 @@ Note that a builder should not be reused after its `build()` method has been cal Although this works nicely, having to instantiate builder classes and call the `build()` method is a bit verbose. So every property setter in the Java API Client also accepts a lambda expression that takes a newly created builder as a parameter and returns a populated builder. The snippet above can also be written as: + +% :::include::start -- do not remove ```java ElasticsearchClient client = ... CreateIndexResponse createResponse = client.indices() @@ -39,11 +54,19 @@ CreateIndexResponse createResponse = client.indices() ) ); ``` +% :::include::end -- do not remove This approach allows for much more concise code, and also avoids importing classes (and even remembering their names) since types are inferred from the method parameter signature. Note in the above example that builder variables are only used to start a chain of property setters. The names of these variables are therefore unimportant and can be shortened to improve readability: + +% :::include::start -- do not remove ```java ElasticsearchClient client = ... CreateIndexResponse createResponse = client.indices() @@ -54,11 +77,19 @@ CreateIndexResponse createResponse = client.indices() ) ); ``` +% :::include::end -- do not remove Builder lambdas become particularly useful with complex nested queries like the one below, taken from the [intervals query API documentation](elasticsearch://reference/query-languages/query-dsl/query-dsl-intervals-query.md). This example also highlights a useful naming convention for builder parameters in deeply nested structures. For lambda expressions with a single argument, Kotlin provides the implicit `it` parameter and Scala allows use of `_`. This can be approximated in Java by using an underscore or a single letter prefix followed by a number representing the depth level (i.e. `_0`, `_1`, or `b0`, `b1` and so on). Not only does this remove the need to create throw-away variable names, but it also improves code readability. Correct indentation also allows the structure of the query to stand out. + +% :::include::start -- do not remove ```java ElasticsearchClient client = ... SearchResponse results = client @@ -92,9 +123,10 @@ SearchResponse results = client ) ) ), - SomeApplicationData.class <1> + SomeApplicationData.class // <1> ); ``` +% :::include::end -- do not remove 1. Search results will be mapped to `SomeApplicationData` instances to be readily available to the application. diff --git a/docs/reference/api-conventions/lists-maps.md b/docs/reference/api-conventions/lists-maps.md index 8e79e3816..9909a588c 100644 --- a/docs/reference/api-conventions/lists-maps.md +++ b/docs/reference/api-conventions/lists-maps.md @@ -12,6 +12,12 @@ Properties of type `List` and `Map` are exposed by object builders as a set of o Object builders create immutable objects, and this also applies to list and map properties that are made immutable at object construction time. + +% :::include::start -- do not remove ```java // Prepare a list of index names List names = Arrays.asList("idx-a", "idx-b", "idx-c"); @@ -47,7 +53,7 @@ SearchRequest search = SearchRequest.of(r -> r a -> a.histogram(h -> h.field("price"))) ); ``` - +% :::include::end -- do not remove ## List and map values are never `null` [_list_and_map_values_are_never_null] @@ -57,6 +63,12 @@ For lists and maps however, applications often only care about whether they’re If you ever need to distinguish between a missing (undefined) optional collection and an effectively-empty collection returned by {{es}}, the `ApiTypeHelper` class provides a utility method to distinguish them: + +% :::include::start -- do not remove ```java NodeStatistics stats = NodeStatistics.of(b -> b .total(1) @@ -72,6 +84,7 @@ assertEquals(0, stats.failures().size()); // - and if needed we can know it was actually not defined assertFalse(ApiTypeHelper.isDefined(stats.failures())); ``` +% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/api-conventions/loading-json.md b/docs/reference/api-conventions/loading-json.md index 06131ba01..a32b27569 100644 --- a/docs/reference/api-conventions/loading-json.md +++ b/docs/reference/api-conventions/loading-json.md @@ -31,17 +31,24 @@ Consider a resource file `some-index.json` containing an index definition: You can create an index from that definition as follows: + +% :::include::start -- do not remove ```java InputStream input = this.getClass() - .getResourceAsStream("some-index.json"); <1> + .getResourceAsStream("some-index.json"); // <1> CreateIndexRequest req = CreateIndexRequest.of(b -> b .index("some-index") - .withJson(input) <2> + .withJson(input) // <2> ); boolean created = client.indices().create(req).acknowledged(); ``` +% :::include::end -- do not remove 1. open an input stream for the JSON resource file. 2. populate the index creation request with the resource file contents. @@ -52,10 +59,16 @@ boolean created = client.indices().create(req).acknowledged(); Similarly, you can read documents to be stored in {{es}} from data files: + +% :::include::start -- do not remove ```java FileReader file = new FileReader(new File(dataDir, "document-1.json")); -IndexRequest req; <1> +IndexRequest req; // <1> req = IndexRequest.of(b -> b .index("some-index") @@ -64,6 +77,7 @@ req = IndexRequest.of(b -> b client.index(req); ``` +% :::include::end -- do not remove 1. when calling `withJson()` on data structures that have generic type parameters, these generic types will be considered to be `JsonData`. @@ -73,6 +87,12 @@ client.index(req); You can combine `withJson()` with regular calls to setter methods. The example below loads the query part of a search request from a `String` and programmatically adds an aggregation. + +% :::include::start -- do not remove ```java Reader queryJson = new StringReader( "{" + @@ -86,8 +106,8 @@ Reader queryJson = new StringReader( "}"); SearchRequest aggRequest = SearchRequest.of(b -> b - .withJson(queryJson) <1> - .aggregations("max-cpu", a1 -> a1 <2> + .withJson(queryJson) // <1> + .aggregations("max-cpu", a1 -> a1 // <2> .dateHistogram(h -> h .field("@timestamp") .calendarInterval(CalendarInterval.Hour) @@ -100,9 +120,10 @@ SearchRequest aggRequest = SearchRequest.of(b -> b ); Map aggs = client - .search(aggRequest, Void.class) <3> + .search(aggRequest, Void.class) // <3> .aggregations(); ``` +% :::include::end -- do not remove 1. loads the query from the JSON string. 2. adds the aggregation. @@ -114,6 +135,12 @@ Map aggs = client The `withJson()` methods are partial deserializers: the properties loaded from the JSON will set property values or replace the previous ones, but will not reset other properties not found in the JSON input. You can use this to combine multiple JSON snippets to build complex search requests. In the example below, we combine separate definitions of a query that selects some documents and an aggregation that is run on the results of this query. + +% :::include::start -- do not remove ```java Reader queryJson = new StringReader( "{" + @@ -124,12 +151,12 @@ Reader queryJson = new StringReader( " }" + " }" + " }," + - " \"size\": 100" + <1> + " \"size\": 100" + // <1> "}"); Reader aggregationJson = new StringReader( "{" + - " \"size\": 0, " + <2> + " \"size\": 0, " + // <2> " \"aggregations\": {" + " \"hours\": {" + " \"date_histogram\": {" + @@ -148,15 +175,16 @@ Reader aggregationJson = new StringReader( "}"); SearchRequest aggRequest = SearchRequest.of(b -> b - .withJson(queryJson) <3> - .withJson(aggregationJson) <4> - .ignoreUnavailable(true) <5> + .withJson(queryJson) // <3> + .withJson(aggregationJson) // <4> + .ignoreUnavailable(true) // <5> ); Map aggs = client .search(aggRequest, Void.class) .aggregations(); ``` +% :::include::end -- do not remove 1. set max number of returned document to 100 for queries. 2. we do not want any matching document in aggregations. diff --git a/docs/reference/api-conventions/variant-types.md b/docs/reference/api-conventions/variant-types.md index 53dda3896..1d58ee527 100644 --- a/docs/reference/api-conventions/variant-types.md +++ b/docs/reference/api-conventions/variant-types.md @@ -13,14 +13,21 @@ This is because variant objects in the Java API Client are implementations of a Variant builders have setter methods for every available implementation. They use the same conventions as regular properties and accept both a builder lambda expression and a ready-made object of the actual type of the variant. Here’s an example to build a term query: + +% :::include::start -- do not remove ```java Query query = new Query.Builder() - .term(t -> t <1> - .field("name") <2> + .term(t -> t // <1> + .field("name") // <2> .value(v -> v.stringValue("foo")) ) - .build(); <3> + .build(); // <3> ``` +% :::include::end -- do not remove 1. Choose the `term` variant to build a term query. 2. Build the terms query with a builder lambda expression. @@ -29,9 +36,16 @@ Query query = new Query.Builder() Variant objects have getter methods for every available implementation. These methods check that the object actually holds a variant of that kind and return the value downcasted to the correct type. They throw an `IllegalStateException` otherwise. This approach allows writing fluent code to traverse variants. + +% :::include::start -- do not remove ```java assertEquals("foo", query.term().value().stringValue()); ``` +% :::include::end -- do not remove Variant objects also provide information on the variant kind they currently hold: @@ -40,12 +54,18 @@ Variant objects also provide information on the variant kind they currently hold This information can then be used to navigate down into specific variants after checking their actual kind: + +% :::include::start -- do not remove +```java +if (query.isTerm()) { // <1> doSomething(query.term()); } -switch(query._kind()) { <2> +switch(query._kind()) { // <2> case Term: doSomething(query.term()); break; @@ -53,9 +73,10 @@ switch(query._kind()) { <2> doSomething(query.intervals()); break; default: - doSomething(query._kind(), query._get()); <3> + doSomething(query._kind(), query._get()); // <3> } ``` +% :::include::end -- do not remove 1. Test if the variant is of a specific kind. 2. Test a larger set of variant kinds. @@ -73,8 +94,14 @@ In the examples below we use a hypothetical plugin that adds a `sphere-distance` To create a custom aggregation, use the `_custom()` aggregation type and provide its identifier, defined by the plugin, and parameters. The parameters can be any object or value that can be serialized to JSON. In the example below we use a simple map: + +% :::include::start -- do not remove ```java -Map params = new HashMap<>(); <1> +Map params = new HashMap<>(); // <1> params.put("interval", 10); params.put("scale", "log"); params.put("origin", new Double[]{145.0, 12.5, 1649.0}); @@ -82,10 +109,11 @@ params.put("origin", new Double[]{145.0, 12.5, 1649.0}); SearchRequest request = SearchRequest.of(r -> r .index("stars") .aggregations("neighbors", agg -> agg - ._custom("sphere-distance", params) <2> + ._custom("sphere-distance", params) // <2> ) ); ``` +% :::include::end -- do not remove 1. Parameters for the custom aggregation. 2. Create a custom aggregation named `neighbors` of kind `sphere-distance` with its parameters. @@ -95,14 +123,20 @@ The results of custom variants are returned as raw JSON represented by a `JsonDa Traversing the JSON tree: + +% :::include::start -- do not remove ```java -SearchResponse response = esClient.search(request, Void.class); <1> +SearchResponse response = esClient.search(request, Void.class); // <1> JsonData neighbors = response .aggregations().get("neighbors") - ._custom(); <2> + ._custom(); // <2> -JsonArray buckets = neighbors.toJson() <3> +JsonArray buckets = neighbors.toJson() // <3> .asJsonObject() .getJsonArray("buckets"); @@ -113,6 +147,7 @@ for (JsonValue item : buckets) { doSomething(key, docCount); } ``` +% :::include::end -- do not remove 1. Use `Void` if you’re only interested in aggregation results, not search hits (see also [Aggregations](/reference/usage/aggregations.md)). 2. Get the `neighbors` aggregation result as custom JSON result. @@ -121,24 +156,37 @@ for (JsonValue item : buckets) { Using a class that represents the custom aggregation results: + +% :::include::start -- do not remove ```java SearchResponse response = esClient.search(request, Void.class); SphereDistanceAggregate neighbors = response .aggregations().get("neighbors") ._custom() - .to(SphereDistanceAggregate.class); <1> + .to(SphereDistanceAggregate.class); // <1> for (Bucket bucket : neighbors.buckets()) { doSomething(bucket.key(), bucket.docCount()); } ``` +% :::include::end -- do not remove 1. Deserialize the custom JSON to a dedicated `SphereDistanceAggregate` class. Where `SphereDistanceAggregate` can be defined as follows: + +% :::include::start -- do not remove ```java public static class SphereDistanceAggregate { private final List buckets; @@ -171,6 +219,7 @@ public static class Bucket { } } ``` +% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/getting-started.md b/docs/reference/getting-started.md index f97d6de02..b12167693 100644 --- a/docs/reference/getting-started.md +++ b/docs/reference/getting-started.md @@ -10,8 +10,8 @@ This page guides you through the installation process of the Java client, shows ### Requirements [_requirements] -* Java 8 or later. -* A JSON object mapping library to allow seamless integration of your application classes with the Elasticsearch API. The examples below show usage with Jackson. +* Java 17 or later. +* A JSON object mapping library to allow seamless integration of your application classes with the Elasticsearch API. The examples below show usage with Jackson, which is the default. ### Installation [_installation] @@ -51,31 +51,30 @@ Refer to the [Installation](setup/installation.md) page to learn more. You can connect to the Elastic Cloud using an API key and the Elasticsearch endpoint. + +% :::include::start -- do not remove ```java // URL and API key String serverUrl = "https://localhost:9200"; String apiKey = "VnVhQ2ZHY0JDZGJrU..."; -// Create the low-level client -RestClient restClient = RestClient - .builder(HttpHost.create(serverUrl)) - .setDefaultHeaders(new Header[]{ - new BasicHeader("Authorization", "ApiKey " + apiKey) - }) - .build(); - -// Create the transport with a Jackson mapper -ElasticsearchTransport transport = new RestClientTransport( - restClient, new JacksonJsonpMapper()); - -// And create the API client -ElasticsearchClient esClient = new ElasticsearchClient(transport); +ElasticsearchClient esClient = ElasticsearchClient.of(b -> b + .host(serverUrl) + .apiKey(apiKey) + // Use the Jackson mapper to deserialize JSON to application objects + .jsonMapper(new JacksonJsonpMapper()) +); // Use the client... // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` +% :::include::end -- do not remove Your Elasticsearch endpoint can be found on the **My deployment** page of your deployment: @@ -101,17 +100,30 @@ Time to use Elasticsearch! This section walks you through the basic, and most im This is how you create the `product` index: + +% :::include::start -- do not remove ```java esClient.indices().create(c -> c .index("products") ); ``` +% :::include::end -- do not remove #### Indexing documents [_indexing_documents] This is a simple way of indexing a document, here a `Product` application object: + +% :::include::start -- do not remove ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -123,17 +135,24 @@ IndexResponse response = esClient.index(i -> i logger.info("Indexed with version " + response.version()); ``` +% :::include::end -- do not remove #### Getting documents [_getting_documents] You can get documents by using the following code: + +% :::include::start -- do not remove ```java GetResponse response = esClient.get(g -> g - .index("products") <1> + .index("products") // <1> .id("bk-1"), - Product.class <2> + Product.class // <2> ); if (response.found()) { @@ -143,6 +162,7 @@ if (response.found()) { logger.info ("Product not found"); } ``` +% :::include::end -- do not remove 1. The get request, with the index name and identifier. 2. The target class, here `Product`. @@ -153,6 +173,12 @@ if (response.found()) { This is how you can create a single match query with the Java client: + +% :::include::start -- do not remove ```java String searchText = "bike"; @@ -167,12 +193,19 @@ SearchResponse response = esClient.search(s -> s Product.class ); ``` +% :::include::end -- do not remove #### Updating documents [_updating_documents] This is how you can update a document, for example to add a new field: + +% :::include::start -- do not remove ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -183,22 +216,37 @@ esClient.update(u -> u Product.class ); ``` +% :::include::end -- do not remove #### Deleting documents [_deleting_documents] + +% :::include::start -- do not remove ```java esClient.delete(d -> d.index("products").id("bk-1")); ``` +% :::include::end -- do not remove #### Deleting an index [_deleting_an_index] + +% :::include::start -- do not remove ```java esClient.indices().delete(d -> d .index("products") ); ``` +% :::include::end -- do not remove ## Examples [_examples] diff --git a/docs/reference/setup/connecting.md b/docs/reference/setup/connecting.md index 4477ff5c5..0323f6769 100644 --- a/docs/reference/setup/connecting.md +++ b/docs/reference/setup/connecting.md @@ -13,31 +13,30 @@ The Java API Client is structured around three main components: This code snippet creates and wires together these three components: + +% :::include::start -- do not remove ```java // URL and API key String serverUrl = "https://localhost:9200"; String apiKey = "VnVhQ2ZHY0JDZGJrU..."; -// Create the low-level client -RestClient restClient = RestClient - .builder(HttpHost.create(serverUrl)) - .setDefaultHeaders(new Header[]{ - new BasicHeader("Authorization", "ApiKey " + apiKey) - }) - .build(); - -// Create the transport with a Jackson mapper -ElasticsearchTransport transport = new RestClientTransport( - restClient, new JacksonJsonpMapper()); - -// And create the API client -ElasticsearchClient esClient = new ElasticsearchClient(transport); +ElasticsearchClient esClient = ElasticsearchClient.of(b -> b + .host(serverUrl) + .apiKey(apiKey) + // Use the Jackson mapper to deserialize JSON to application objects + .jsonMapper(new JacksonJsonpMapper()) +); // Use the client... // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` +% :::include::end -- do not remove Authentication is managed by the [Java Low Level REST Client](/reference/transport/rest-client/index.md). For further details on configuring authentication, refer to [its documentation](/reference/transport/rest-client/config/basic_authentication.md). @@ -48,6 +47,12 @@ The code snippet below searches all items from a “product” index whose name It illustrates the use of fluent functional builders to write search queries as concise DSL-like code. This pattern is explained in more detail in [*API conventions*](/reference/api-conventions/index.md). + +% :::include::start -- do not remove ```java SearchResponse search = esClient.search(s -> s .index("products") @@ -62,7 +67,7 @@ for (Hit hit: search.hits().hits()) { processProduct(hit.source()); } ``` - +% :::include::end -- do not remove ## Using a secure connection [using-a-secure-connection] @@ -72,7 +77,7 @@ In self-managed installations, Elasticsearch will start with security features l When you start Elasticsearch for the first time you’ll see a distinct block like the one below in the output from Elasticsearch (you may have to scroll up if it’s been a while): -```xml +``` -> Elasticsearch security features have been automatically configured! -> Authentication is enabled and cluster connections are encrypted. @@ -93,34 +98,30 @@ Depending on the context, you have two options for verifying the HTTPS connectio This method of verifying the HTTPS connection uses the certificate fingerprint value noted down earlier. + +% :::include::start -- do not remove ```java String fingerprint = ""; SSLContext sslContext = TransportUtils - .sslContextFromCaFingerprint(fingerprint); <1> + .sslContextFromCaFingerprint(fingerprint); // <1> -BasicCredentialsProvider credsProv = new BasicCredentialsProvider(); <2> -credsProv.setCredentials( - AuthScope.ANY, new UsernamePasswordCredentials(login, password) +ElasticsearchClient esClient = ElasticsearchClient.of(b -> b + .host(url) // <3> + .usernameAndPassword(login, password) // <2> + .sslContext(sslContext) // <4> ); -RestClient restClient = RestClient - .builder(new HttpHost(host, port, "https")) <3> - .setHttpClientConfigCallback(hc -> hc - .setSSLContext(sslContext) <4> - .setDefaultCredentialsProvider(credsProv) - ) - .build(); - -// Create the transport and the API client -ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); -ElasticsearchClient esClient = new ElasticsearchClient(transport); - // Use the client... // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` +% :::include::end -- do not remove 1. Create an `SSLContext` with the certificate fingerprint. 2. Set up authentication. @@ -148,34 +149,30 @@ The generated root CA certificate can be found in the `certs` directory in your Once you have made the `http_ca.crt` file available to your application, you can use it to set up the client: + +% :::include::start -- do not remove ```java File certFile = new File("/path/to/http_ca.crt"); SSLContext sslContext = TransportUtils - .sslContextFromHttpCaCrt(certFile); <1> + .sslContextFromHttpCaCrt(certFile); // <1> -BasicCredentialsProvider credsProv = new BasicCredentialsProvider(); <2> -credsProv.setCredentials( - AuthScope.ANY, new UsernamePasswordCredentials(login, password) +ElasticsearchClient esClient = ElasticsearchClient.of(b -> b + .host(url) // <3> + .usernameAndPassword(login, password) // <2> + .sslContext(sslContext) // <4> ); -RestClient restClient = RestClient - .builder(new HttpHost(host, port, "https")) <3> - .setHttpClientConfigCallback(hc -> hc - .setSSLContext(sslContext) <4> - .setDefaultCredentialsProvider(credsProv) - ) - .build(); - -// Create the transport and the API client -ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); -ElasticsearchClient esClient = new ElasticsearchClient(transport); - // Use the client... // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` +% :::include::end -- do not remove 1. Create an `SSLContext` with the `http_ca.crt` file. 2. Set up authentication. diff --git a/docs/reference/setup/installation.md b/docs/reference/setup/installation.md index dacc9767d..366126a4b 100644 --- a/docs/reference/setup/installation.md +++ b/docs/reference/setup/installation.md @@ -15,9 +15,9 @@ Releases are hosted on [Maven Central](https://search.maven.org/search?q=g:co.el ## Installation in a Gradle project by using Jackson [gradle] -```groovy +```groovy subs=true dependencies { - implementation 'co.elastic.clients:elasticsearch-java:9.0.0-beta1' + implementation 'co.elastic.clients:elasticsearch-java:{{version}}' } ``` @@ -33,7 +33,7 @@ In the `pom.xml` of your project, add the following repository definition and de co.elastic.clients elasticsearch-java - 9.0.0-beta1 + {{version}} @@ -50,7 +50,7 @@ If this happens, you have to explicitly add the `jakarta.json:jakarta.json-api:2 ```groovy dependencies { ... - implementation 'jakarta.json:jakarta.json-api:2.0.1' + implementation 'jakarta.json:jakarta.json-api:2.1.3' } ``` @@ -61,7 +61,7 @@ dependencies { jakarta.json jakarta.json-api - 2.0.1 + 2.1.3 @@ -74,8 +74,6 @@ Some frameworks like Spring Boot or Helidon come with their Gradle and Maven plu One of these libraries can be `jakarta.json:json-api` that defines the standard Java JSON API. In version `1.x` this library used the `javax.json` package, while in version `2.x` it uses the `jakarta.json` package after [the transition from JavaEE to JakartaEE](https://blogs.oracle.com/javamagazine/post/transition-from-java-ee-to-jakarta-ee). -The Java API Client depends on version `2.0.1` of this library, in order to use the newer and future-proof `jakarta.json` package. But some build plugins and BOMs override the Java API Client’s dependency to use version `1.x` in the older `javax.json` namespace, resulting in `ClassNotFoundException: jakarta.json.spi.JsonProvider`. +The Java API Client depends on version `2.1.3` of this library, in order to use the newer and future-proof `jakarta.json` package. But some build plugins and BOMs override the Java API Client’s dependency to use version `1.x` in the older `javax.json` namespace, resulting in `ClassNotFoundException: jakarta.json.spi.JsonProvider`. Adding the correct version as top-level project dependency solves the problem. - -If your application also requires `javax.json` you can add the `javax.json:javax.json-api:1.1.4` dependency, which is equivalent to `jakarta.json:jakarta.json-api:1.1.6`. diff --git a/docs/reference/setup/opentelemetry.md b/docs/reference/setup/opentelemetry.md index 723e5d173..70310e8c1 100644 --- a/docs/reference/setup/opentelemetry.md +++ b/docs/reference/setup/opentelemetry.md @@ -40,18 +40,17 @@ When using the [OpenTelemetry Java SDK manually](https://opentelemetry.io/docs/i In case you are using [manual OpenTelemetry instrumentation](https://opentelemetry.io/docs/instrumentation/java/manual/#example) with a custom OpenTelemetry SDK instance that is *not registered globally*, you can create the Java API Client using a custom OpenTelemetry instance. The following code snippet shows an example of using a custom OpenTelemetry instance. + +% :::include::start -- do not remove ```java // URL and API key String serverUrl = "https://localhost:9200"; String apiKey = "VnVhQ2ZHY0JDZGJrU..."; -// Create the low-level client -RestClient restClient = RestClient - .builder(HttpHost.create(serverUrl)) - .setDefaultHeaders(new Header[]{ - new BasicHeader("Authorization", "ApiKey " + apiKey) - }) - .build(); // Create and configure custom OpenTelemetry instance OpenTelemetry customOtel = OpenTelemetrySdk.builder().build(); @@ -60,19 +59,18 @@ OpenTelemetry customOtel = OpenTelemetrySdk.builder().build(); OpenTelemetryForElasticsearch esOtelInstrumentation = new OpenTelemetryForElasticsearch(customOtel, false); -// Create the transport with the custom Instrumentation instance -ElasticsearchTransport transport = new RestClientTransport( - restClient, new JacksonJsonpMapper(), null, esOtelInstrumentation +ElasticsearchClient esClient = ElasticsearchClient.of(b -> b + .host(serverUrl) + .apiKey(apiKey) + .instrumentation(esOtelInstrumentation) ); -// And create the API client -ElasticsearchClient esClient = new ElasticsearchClient(transport); - // Use the client... // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` +% :::include::end -- do not remove ## Configuring the OpenTelemetry instrumentation [_configuring_the_opentelemetry_instrumentation] diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml index db894666d..094953c5c 100644 --- a/docs/reference/toc.yml +++ b/docs/reference/toc.yml @@ -1,3 +1,6 @@ +# Note: source code snippets in all the docs are pulled from test source files, do ensure their correctness. +# To update these snippets, run `./gradlew :tools:expand-includes` from the main elasticsearch-java directory. + toc: - file: index.md - file: getting-started.md diff --git a/docs/reference/usage/aggregations.md b/docs/reference/usage/aggregations.md index 2a464ceb1..afc4c6154 100644 --- a/docs/reference/usage/aggregations.md +++ b/docs/reference/usage/aggregations.md @@ -21,6 +21,12 @@ This example is an analytics-type aggregation where we do not want to use the ma If that same aggregation was used for to display products and the price histogram as drill-down facets, we would have set `size` to a non-zero value and used `Product` as the target class to process the results. + +% :::include::start -- do not remove ```java String searchText = "bike"; @@ -31,17 +37,18 @@ Query query = MatchQuery.of(m -> m SearchResponse response = esClient.search(b -> b .index("products") - .size(0) <1> - .query(query) <2> - .aggregations("price-histogram", a -> a <3> - .histogram(h -> h <4> + .size(0) // <1> + .query(query) // <2> + .aggregations("price-histogram", a -> a // <3> + .histogram(h -> h // <4> .field("price") .interval(50.0) ) ), - Void.class <5> + Void.class // <5> ); ``` +% :::include::end -- do not remove 1. Set the number of matching documents to zero as we only use the price histogram. 2. Set the query that fill filter the products on which to run the aggregation @@ -52,17 +59,25 @@ SearchResponse response = esClient.search(b -> b The response contains an aggregation result for each aggregation in the request. + +% :::include::start -- do not remove ```java List buckets = response.aggregations() - .get("price-histogram") <1> - .histogram() <2> - .buckets().array(); <3> + .get("price-histogram") // <1> + .histogram() // <2> + .buckets().array(); // <3> for (HistogramBucket bucket: buckets) { logger.info("There are " + bucket.docCount() + " bikes under " + bucket.key()); } + ``` +% :::include::end -- do not remove 1. Get the results for the "price-histogram" aggregation. 2. Cast it down to the `histogram` variant results. This has to be consistent with the aggregation definition. diff --git a/docs/reference/usage/indexing-bulk.md b/docs/reference/usage/indexing-bulk.md index 6848512e9..dd2a4db05 100644 --- a/docs/reference/usage/indexing-bulk.md +++ b/docs/reference/usage/indexing-bulk.md @@ -26,15 +26,21 @@ A `BulkRequest` contains a collection of operations, each operation being a [typ The example below shows how to index a list or application objects. + +% :::include::start -- do not remove ```java List products = fetchProducts(); BulkRequest.Builder br = new BulkRequest.Builder(); for (Product product : products) { - br.operations(op -> op <1> - .index(idx -> idx <2> - .index("products") <3> + br.operations(op -> op // <1> + .index(idx -> idx // <2> + .index("products") // <3> .id(product.getSku()) .document(product) ) @@ -53,6 +59,7 @@ if (result.errors()) { } } ``` +% :::include::end -- do not remove 1. Adds an operation (remember that [list properties are additive](/reference/api-conventions/lists-maps.md)). `op` is is a builder for `BulkOperation` which is a [variant type](/reference/api-conventions/variant-types.md). This type has `index`, `create`, `update` and `delete` variants. 2. Selects the `index` operation variant, `idx` is a builder for `IndexOperation`. @@ -66,6 +73,12 @@ The `document` property of a bulk index request can be any object that can be se In the example below we will use the Java API Client’s `BinaryData` to read json files from a log directory and send them in a bulk request. + +% :::include::start -- do not remove ```java // List json log files in the log directory File[] logFiles = logDir.listFiles( @@ -86,6 +99,7 @@ for (File file: logFiles) { ); } ``` +% :::include::end -- do not remove ## Streaming ingestion with the Bulk Ingester [_streaming_ingestion_with_the_bulk_ingester] @@ -100,18 +114,24 @@ The ingester will send a bulk request when one of the following criteria is met: Additionally, you can define a maximum number of concurrent request waiting to be executed by {{es}} (defaults to 1). When that maximum is reached and the maximum number of operations have been collected, adding a new operation to the indexer will block. This is avoids overloading the {{es}} server by putting backpressure on the client application. + +% :::include::start -- do not remove ```java BulkIngester ingester = BulkIngester.of(b -> b - .client(esClient) <1> - .maxOperations(100) <2> - .flushInterval(1, TimeUnit.SECONDS) <3> + .client(esClient) // <1> + .maxOperations(100) // <2> + .flushInterval(1, TimeUnit.SECONDS) // <3> ); for (File file: logFiles) { FileInputStream input = new FileInputStream(file); BinaryData data = BinaryData.of(IOUtils.toByteArray(input), ContentType.APPLICATION_JSON); - ingester.add(op -> op <4> + ingester.add(op -> op // <4> .index(idx -> idx .index("logs") .document(data) @@ -119,8 +139,9 @@ for (File file: logFiles) { ); } -ingester.close(); <5> +ingester.close(); // <5> ``` +% :::include::end -- do not remove 1. Sets the {{es}} client used to send bulk requests. 2. Sets the maximum number of operations to collect before sending a bulk request. @@ -133,8 +154,14 @@ Additionally, the bulk ingester accepts a listener so that your application can The following example shows how you can use context values to implement a bulk ingestion listener: as previously it sends JSON log files in bulk, but tracks bulk request errors and failed operations. When an operation fails, depending on the error type you may want to re-add it to the ingester. + +% :::include::start -- do not remove ```java -BulkListener listener = new BulkListener() { <1> +BulkListener listener = new BulkListener() { // <1> @Override public void beforeBulk(long executionId, BulkRequest request, List contexts) { } @@ -164,7 +191,7 @@ BulkIngester ingester = BulkIngester.of(b -> b .client(esClient) .maxOperations(100) .flushInterval(1, TimeUnit.SECONDS) - .listener(listener) <2> + .listener(listener) // <2> ); for (File file: logFiles) { @@ -176,12 +203,13 @@ for (File file: logFiles) { .index("logs") .document(data) ), - file.getName() <3> + file.getName() // <3> ); } ingester.close(); ``` +% :::include::end -- do not remove 1. Creates a listener where context values are strings for the ingested file name. 2. Registers the listener on the bulk ingester. diff --git a/docs/reference/usage/indexing.md b/docs/reference/usage/indexing.md index 469696cff..92b105bbc 100644 --- a/docs/reference/usage/indexing.md +++ b/docs/reference/usage/indexing.md @@ -19,6 +19,12 @@ See the [{{es}} API documentation](https://www.elastic.co/docs/api/doc/elasticse The most direct way to build requests is using the fluent DSL. In the example below we index a product description in the `products` index, using the product’s SKU as the document identifier in the index. The `product` object will be mapped to JSON using the object mapper configured on the {{es}} client. + +% :::include::start -- do not remove ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -30,9 +36,16 @@ IndexResponse response = esClient.index(i -> i logger.info("Indexed with version " + response.version()); ``` +% :::include::end -- do not remove You can also assign objects created with the DSL to variables. Java API Client classes have a static `of()` method for this, that creates an object with the DSL syntax. + +% :::include::start -- do not remove ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -46,12 +59,18 @@ IndexResponse response = esClient.index(request); logger.info("Indexed with version " + response.version()); ``` - +% :::include::end -- do not remove ## Using classic builders [_using_classic_builders] If you’re more used to the classic builder pattern, it is also available. Builder objects are used under the hood by the fluent DSL syntax. + +% :::include::start -- do not remove ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -64,12 +83,19 @@ IndexResponse response = esClient.index(indexReqBuilder.build()); logger.info("Indexed with version " + response.version()); ``` +% :::include::end -- do not remove ## Using the asynchronous client [_using_the_asynchronous_client] The examples above used the synchronous {{es}} client. All {{es}} APIs are also available in the asynchronous client, using the same request and response types. See also [Blocking and asynchronous clients](/reference/api-conventions/blocking-async.md) for additional details. + +% :::include::start -- do not remove ```java ElasticsearchAsyncClient esAsyncClient = new ElasticsearchAsyncClient(transport); @@ -87,6 +113,7 @@ esAsyncClient.index(i -> i } }); ``` +% :::include::end -- do not remove ## Using raw JSON data [_using_raw_json_data] @@ -95,6 +122,12 @@ When the data you want to index comes from external sources, having to create do You can index data from an arbitrary source using `withJson()`. Using this method will read the source and use it for the index request’s `document` property. See [Creating API objects from JSON data](/reference/api-conventions/loading-json.md) for additional details. + +% :::include::start -- do not remove ```java Reader input = new StringReader( "{'@timestamp': '2022-04-08T13:55:32Z', 'level': 'warn', 'message': 'Some log message'}" @@ -109,6 +142,7 @@ IndexResponse response = esClient.index(request); logger.info("Indexed with version " + response.version()); ``` +% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/usage/reading.md b/docs/reference/usage/reading.md index 39aefc7b3..b9b711ff4 100644 --- a/docs/reference/usage/reading.md +++ b/docs/reference/usage/reading.md @@ -22,11 +22,17 @@ The `get` request has two parameters: * the first parameter is the actual request, built below with the fluent DSL * the second parameter is the class we want the document’s JSON to be mapped to. + +% :::include::start -- do not remove ```java GetResponse response = esClient.get(g -> g - .index("products") <1> + .index("products") // <1> .id("bk-1"), - Product.class <2> + Product.class // <2> ); if (response.found()) { @@ -36,6 +42,7 @@ if (response.found()) { logger.info ("Product not found"); } ``` +% :::include::end -- do not remove 1. The get request, with the index name and identifier. 2. The target class, here `Product`. @@ -48,11 +55,17 @@ When your index contains semi-structured data or if you don’t have a domain ob Raw JSON data is just another class that you can use as the result type for the get request. In the example below we use Jackson’s `ObjectNode`. We could also have used any JSON representation that can be deserialized by the JSON mapper associated to the `ElasticsearchClient`. + +% :::include::start -- do not remove ```java GetResponse response = esClient.get(g -> g .index("products") .id("bk-1"), - ObjectNode.class <1> + ObjectNode.class // <1> ); if (response.found()) { @@ -63,6 +76,7 @@ if (response.found()) { logger.info("Product not found"); } ``` +% :::include::end -- do not remove 1. The target class is a raw JSON object. diff --git a/docs/reference/usage/searching.md b/docs/reference/usage/searching.md index 0affa3c57..810d02cb0 100644 --- a/docs/reference/usage/searching.md +++ b/docs/reference/usage/searching.md @@ -23,18 +23,25 @@ The total value comes with a relation that indicates if the total is exact (`eq` Each returned document comes with its relevance score and additional information about its location in the index. + +% :::include::start -- do not remove +```java + String searchText = "bike"; SearchResponse response = esClient.search(s -> s - .index("products") <1> - .query(q -> q <2> - .match(t -> t <3> - .field("name") <4> + .index("products") // <1> + .query(q -> q // <2> + .match(t -> t // <3> + .field("name") // <4> .query(searchText) ) ), - Product.class <5> + Product.class // <5> ); TotalHits total = response.hits().total(); @@ -52,6 +59,7 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` +% :::include::end -- do not remove 1. Name of the index we want to search. 2. The query part of the search request (a search request can also have other components like [aggregations](aggregations.md)). @@ -67,29 +75,35 @@ Similarly to [get](reading.md) operations, you can fetch documents matching your {{es}} allows individual queries to be combined to build more complex search requests. In the example below we will search for bikes with a maximum price of 200. + +% :::include::start -- do not remove ```java String searchText = "bike"; double maxPrice = 200.0; // Search by product name -Query byName = MatchQuery.of(m -> m <1> +Query byName = MatchQuery.of(m -> m // <1> .field("name") .query(searchText) -)._toQuery(); <2> +)._toQuery(); // <2> // Search by max price Query byMaxPrice = RangeQuery.of(r -> r .number(n -> n .field("price") - .gte(maxPrice)) <3> + .gte(maxPrice)) // <3> )._toQuery(); // Combine name and price queries to search the product index SearchResponse response = esClient.search(s -> s .index("products") .query(q -> q - .bool(b -> b <4> - .must(byName) <5> + .bool(b -> b // <4> + .must(byName) // <5> .must(byMaxPrice) ) ), @@ -102,6 +116,7 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` +% :::include::end -- do not remove 1. We’re creating the queries for individual criteria separately. 2. A `MatchQuery` is a query *variant* that we have to turn into the `Query` *union type*. See [variant types](/reference/api-conventions/variant-types.md) for additional details. @@ -117,26 +132,39 @@ A search template is a stored search that you can run with different variables. Before running a template search, you first have to create the template. This is a stored script that returns the search request body, and is usually defined as a Mustache template. This stored script can be created outside the application, and also with the Java API Client: + +% :::include::start -- do not remove ```java // Create a script esClient.putScript(r -> r - .id("query-script") <1> + .id("query-script") // <1> .script(s -> s .lang("mustache") - .source("{\"query\":{\"match\":{\"{{field}}\":\"{{value}}\"}}}") + .source(so -> so.scriptString("{\"query\":{\"match\":{\"{{field}}\":\"{{value}}\"}}}")) )); ``` +% :::include::end -- do not remove 1. Identifier of the template script to create. To use the search template, use the `searchTemplate` method to refer to the script and provide values for its parameters: + +% :::include::start -- do not remove ```java SearchTemplateResponse response = esClient.searchTemplate(r -> r .index("some-index") - .id("query-script") <1> - .params("field", JsonData.of("some-field")) <2> + .id("query-script") // <1> + .params("field", JsonData.of("some-field")) // <2> .params("value", JsonData.of("some-data")), Product.class ); @@ -147,6 +175,7 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` +% :::include::end -- do not remove 1. Identifier of the template script to use. 2. Template parameter values. diff --git a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java index f0a579ea6..66d116ac3 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java @@ -75,11 +75,11 @@ public void loadIndexDefinition() throws IOException { //tag::load-index InputStream input = this.getClass() - .getResourceAsStream("some-index.json"); //<1> + .getResourceAsStream("some-index.json"); // <1> CreateIndexRequest req = CreateIndexRequest.of(b -> b .index("some-index") - .withJson(input) //<2> + .withJson(input) // <2> ); boolean created = client.indices().create(req).acknowledged(); @@ -89,13 +89,13 @@ public void loadIndexDefinition() throws IOException { @Disabled @Test public void ingestDocument() throws IOException { - + File dataDir = null; //tag::ingest-data FileReader file = new FileReader(new File(dataDir, "document-1.json")); - IndexRequest req; //<1> + IndexRequest req; // <1> req = IndexRequest.of(b -> b .index("some-index") @@ -123,8 +123,8 @@ public void query1() throws IOException { "}"); SearchRequest aggRequest = SearchRequest.of(b -> b - .withJson(queryJson) //<1> - .aggregations("max-cpu", a1 -> a1 //<2> + .withJson(queryJson) // <1> + .aggregations("max-cpu", a1 -> a1 // <2> .dateHistogram(h -> h .field("@timestamp") .calendarInterval(CalendarInterval.Hour) @@ -137,7 +137,7 @@ public void query1() throws IOException { ); Map aggs = client - .search(aggRequest, Void.class) //<3> + .search(aggRequest, Void.class) // <3> .aggregations(); //end::query } @@ -156,12 +156,12 @@ public void query2() throws IOException { " }" + " }" + " }," + - " \"size\": 100" + //<1> + " \"size\": 100" + // <1> "}"); Reader aggregationJson = new StringReader( "{" + - " \"size\": 0, " + //<2> + " \"size\": 0, " + // <2> " \"aggregations\": {" + " \"hours\": {" + " \"date_histogram\": {" + @@ -180,9 +180,9 @@ public void query2() throws IOException { "}"); SearchRequest aggRequest = SearchRequest.of(b -> b - .withJson(queryJson) //<3> - .withJson(aggregationJson) //<4> - .ignoreUnavailable(true) //<5> + .withJson(queryJson) // <3> + .withJson(aggregationJson) // <4> + .ignoreUnavailable(true) // <5> ); Map aggs = client diff --git a/java-client/src/test/java/co/elastic/clients/documentation/usage/IndexingBulkTest.java b/java-client/src/test/java/co/elastic/clients/documentation/usage/IndexingBulkTest.java index 1e6006978..33997effd 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/usage/IndexingBulkTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/usage/IndexingBulkTest.java @@ -71,9 +71,9 @@ public void indexBulk() throws Exception { BulkRequest.Builder br = new BulkRequest.Builder(); for (Product product : products) { - br.operations(op -> op //<1> - .index(idx -> idx //<2> - .index("products") //<3> + br.operations(op -> op // <1> + .index(idx -> idx // <2> + .index("products") // <3> .id(product.getSku()) .document(product) ) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8e5d8b347..868426732 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ import java.nio.file.Paths rootProject.name = "elasticsearch-java" include("java-client") +include("tools") diff --git a/tools/build.gradle.kts b/tools/build.gradle.kts new file mode 100644 index 000000000..ec22c992f --- /dev/null +++ b/tools/build.gradle.kts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + java + `java-test-fixtures` +} + +tasks.withType { + useJUnitPlatform() +} + +java { + targetCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + // EPL-2.0 + // https://junit.org/junit5/ + testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0") +} + +tasks.register("expand-includes") { + group = "application" + mainClass.set("co.elastic.clients.tools.docs.IncludeExpander") + args = listOf( + "../docs/reference", + ) + classpath = sourceSets["main"].runtimeClasspath +} + +repositories { + mavenCentral() +} diff --git a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java new file mode 100644 index 000000000..067bea89f --- /dev/null +++ b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java @@ -0,0 +1,264 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package co.elastic.clients.tools.docs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IncludeExpander { + + public static void main(String[] args) throws IOException { + + File dir = new File(args[0]); + + if (!dir.isDirectory()) { + throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory"); + } + + processDirectory(dir, Map.of("doc-tests-src", "../java-client/src/test/java/co/elastic/clients/documentation")); + } + + public static void processDirectory(File dir, Map subst) throws IOException { + System.out.println("Processing directory " + dir); + // Traverse all files + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + processDirectory(file, subst); + + } else if (file.getName().endsWith(".md")) { + processFile(file, subst); + } + } + } + + public static void processFile(File file, Map subst) throws IOException { + Path path = file.toPath(); + String text = Files.readString(path); + + String result = expandText(text, subst, path.toString()); + if (result == null) { + // No include directives + return; + } + + if (result.equals(text)) { + System.out.println("No change in " + path); + return; + } + + // File changed, write it. + System.out.println("Expanded " + path); + Files.writeString(path, result); + } + + enum State { + NORMAL_TEXT, + INCLUDE_DIRECTIVE, + INCLUDE_EXPANSION, + } + + public static void fail(String message, String path, LineNumberReader reader, Throwable e) { + throw new RuntimeException(message + " at " + path + ":" + reader.getLineNumber(), e); + } + + public static String expandText(String input, Map subst, String path) throws IOException { + int start = input.indexOf("")) { + // End of template: append expanded output with enclosing markers + output.append("% :::include::start -- do not remove").append("\n"); + output.append(expanded); + output.append("% :::include::end -- do not remove").append("\n"); + state = State.NORMAL_TEXT; + + } else { + // Regular line in the include template + expanded.append(line).append("\n"); + } + } + } + } + + if (state != State.NORMAL_TEXT) { + throw new RuntimeException("Premature end of file in " + state); + } + + return output.toString(); + } + + // Extracts path and tag from ":::include some/path/to/Source.java[tag-name]" + public static Pattern DIRECTIVE = Pattern.compile(":::\\{include}\\s+([^\\[]+)\\[([^]]*).*"); + + public static void expandIncludeDirective(String input, Map subst, StringBuilder output) throws IOException { + + Matcher matcher = DIRECTIVE.matcher(input); + + if (!matcher.matches()) { + throw new RuntimeException("Invalid directive: " + input); + } + + String path = matcher.group(1); + String tag = matcher.group(2); + + // Brute force replacement of placeholders + for (var kv: subst.entrySet()) { + path = path.replace("{" + kv.getKey() + "}", kv.getValue()); + } + + expandTaggedFile(path, tag, output); + } + + public static void expandTaggedFile(String path, String tag, StringBuilder output) throws IOException { + File file = new File(path); + String content = Files.readString(file.toPath()); + var reader = new BufferedReader(new StringReader(content)); + + String startTag = "//tag::" + tag; + String endTag = "//end::" + tag; + + String line; + int pos; + boolean found = false; + while ((line = reader.readLine()) != null) { + if ((pos = line.indexOf(startTag)) >= 0) { + found = true; + while((line = reader.readLine()) != null) { + if (line.contains(endTag)) { + return; + } + // If the line has more characters than the tag's initial position, + // assume it's whitespace and truncate it to remove indentation. + if (line.length() > pos) { + line = line.substring(pos); + } + output.append(line).append("\n"); + } + } + } + + if (found) { + throw new RuntimeException("Missing end tag '" + tag + "' in " + path); + } else { + throw new RuntimeException("Missing start tag '" + tag + "' in " + path); + } + } + + // TODO: write proper tests + public static void main0(String[] args) throws IOException { + var string = ":::{include} {doc-tests-src}/api_conventions/ApiConventionsTest.java[blocking-and-async]"; + + Matcher matcher = DIRECTIVE.matcher(string); + if (matcher.matches()) { + for (int i = 1; i <= matcher.groupCount(); i++) { + System.out.println(i + " - " + matcher.group(i)); + } + } else { + System.out.println("No match found"); + } + + } + + + public static void main1(String[] args) throws IOException { + + System.out.println(expandText(""" + Text before + + + % :::include::start -- do not remove + + ```java + ElasticsearchTransport transport = ... + + ``` + + % :::include::end -- do not remove + + Text after + """, + Map.of("doc-tests-src", "java-client/src/test/java/co/elastic/clients/documentation"), + "some-path" + )); + + } +} From 1eb88134487be1d2c16649813c006eafba365827 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Tue, 8 Apr 2025 19:34:17 +0200 Subject: [PATCH 03/14] Fix links --- docs/reference/index.md | 2 +- docs/reference/setup/opentelemetry.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/index.md b/docs/reference/index.md index 591fad7f2..099edb0ae 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -15,7 +15,7 @@ This is the documentation for the official Java API Client for {{es}}. The clien * Blocking and asynchronous versions of all APIs. * Use of fluent builders and functional patterns to allow writing concise yet readable code when creating complex nested structures. * Seamless integration of application classes by using an object mapper such as Jackson or any JSON-B implementation. -* Delegates protocol handling to an http client such as the [Java Low Level REST Client](transport/rest-client/) that takes care of all transport-level concerns: HTTP connection pooling, retries, node discovery, and so on. +* Delegates protocol handling to an http client such as the [Java Low Level REST Client](transport/rest-client/index.md) that takes care of all transport-level concerns: HTTP connection pooling, retries, node discovery, and so on. ## Elasticsearch server compatibility policy [_elasticsearch_server_compatibility_policy] diff --git a/docs/reference/setup/opentelemetry.md b/docs/reference/setup/opentelemetry.md index 70310e8c1..6f5ad6fde 100644 --- a/docs/reference/setup/opentelemetry.md +++ b/docs/reference/setup/opentelemetry.md @@ -9,21 +9,21 @@ You can use [OpenTelemetry](https://opentelemetry.io/) to monitor the performanc The native instrumentation in the Java API Client follows the [OpenTelemetry Semantic Conventions for {{es}}](https://opentelemetry.io/docs/specs/semconv/database/elasticsearch/). In particular, the instrumentation in the client covers the logical layer of {{es}} requests. A single span per request is created that is processed by the service through the Java API Client. The following image shows a trace that records the handling of three different {{es}} requests: an `index`, `get` and a search `request`: -:::{image} images/otel-waterfall-instrumented-without-http.jpg +:::{image} ../images/otel-waterfall-instrumented-without-http.jpg :alt: Distributed trace with {{es}} spans :class: screenshot ::: Usually, OpenTelemetry agents and auto-instrumentation modules come with instrumentation support for HTTP-level communication. In this case, in addition to the logical {{es}} client requests, spans will be captured for the physical HTTP requests emitted by the client. The following image shows a trace with both, {{es}} spans (in blue) and the corresponding HTTP-level spans (in red): -:::{image} images/otel-waterfall-instrumented.jpg +:::{image} ../images/otel-waterfall-instrumented.jpg :alt: Distributed trace with {{es}} and HTTP spans :class: screenshot ::: Advanced Java API Client behavior such as nodes round-robin and request retries are revealed through the combination of logical {{es}} spans and the physical HTTP spans. The following example shows an `index` request in a scenario with two {{es}} nodes: -:::{image} images/otel-waterfall-retries.jpg +:::{image} ../images/otel-waterfall-retries.jpg :alt: Distributed trace with request retries :class: screenshot ::: From 71696923679da25be19bcd0fb6662be5a2dd019f Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Tue, 8 Apr 2025 19:39:02 +0200 Subject: [PATCH 04/14] Checkstyle --- .../co/elastic/clients/elasticsearch/model/OverloadsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/OverloadsTest.java b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/OverloadsTest.java index a09939b58..5216e151e 100644 --- a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/OverloadsTest.java +++ b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/OverloadsTest.java @@ -27,7 +27,6 @@ import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.testkit.ModelTestCase; -import org.junit.Ignore; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; From 14344d059da4e4369ab45a79f4947ac0584ac4a8 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 07:33:07 +0200 Subject: [PATCH 05/14] Simplify (a lot) code includes --- .../api-conventions/blocking-async.md | 10 +- .../api-conventions/building-objects.md | 44 +----- docs/reference/api-conventions/lists-maps.md | 16 +- .../reference/api-conventions/loading-json.md | 32 +--- .../api-conventions/variant-types.md | 58 +------- docs/reference/getting-started.md | 71 +-------- docs/reference/setup/connecting.md | 32 +--- docs/reference/setup/opentelemetry.md | 9 +- docs/reference/usage/aggregations.md | 16 +- docs/reference/usage/indexing-bulk.md | 37 +---- docs/reference/usage/indexing.md | 42 +----- docs/reference/usage/reading.md | 16 +- docs/reference/usage/searching.md | 32 +--- .../api_conventions/ApiConventionsTest.java | 15 +- tools/build.gradle.kts | 3 +- .../clients/tools/docs/IncludeExpander.java | 137 +++++------------- 16 files changed, 99 insertions(+), 471 deletions(-) diff --git a/docs/reference/api-conventions/blocking-async.md b/docs/reference/api-conventions/blocking-async.md index 5e0786246..bdea495b0 100644 --- a/docs/reference/api-conventions/blocking-async.md +++ b/docs/reference/api-conventions/blocking-async.md @@ -9,15 +9,8 @@ API clients come in two flavors: blocking and asynchronous. All methods on async Both flavors can be used at the same time depending on your needs, sharing the same transport object: - -% :::include::start -- do not remove -```java -ElasticsearchTransport transport = ... // Synchronous blocking client ElasticsearchClient client = new ElasticsearchClient(transport); @@ -39,7 +32,6 @@ asyncClient } }); ``` -% :::include::end -- do not remove Although we won’t go in deeper details on asynchronous programming in Java, remember to handle failures of asynchronous tasks. It’s easy to overlook them and have errors go unnoticed. diff --git a/docs/reference/api-conventions/building-objects.md b/docs/reference/api-conventions/building-objects.md index 677144b3f..8a2c4f6ba 100644 --- a/docs/reference/api-conventions/building-objects.md +++ b/docs/reference/api-conventions/building-objects.md @@ -10,15 +10,9 @@ mapped_pages: All data types in the Java API Client are immutable. Object creation uses the [builder pattern](https://www.informit.com/articles/article.aspx?p=1216151&seqNum=2) that was popularized in **Effective Java** in 2008. - -% :::include::start -- do not remove -```java -ElasticsearchClient client = ... +ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices().create( new CreateIndexRequest.Builder() .index("my-index") @@ -28,7 +22,6 @@ CreateIndexResponse createResponse = client.indices().create( .build() ); ``` -% :::include::end -- do not remove Note that a builder should not be reused after its `build()` method has been called. @@ -37,15 +30,9 @@ Note that a builder should not be reused after its `build()` method has been cal Although this works nicely, having to instantiate builder classes and call the `build()` method is a bit verbose. So every property setter in the Java API Client also accepts a lambda expression that takes a newly created builder as a parameter and returns a populated builder. The snippet above can also be written as: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas ```java -ElasticsearchClient client = ... +ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() .create(createIndexBuilder -> createIndexBuilder .index("my-index") @@ -54,21 +41,14 @@ CreateIndexResponse createResponse = client.indices() ) ); ``` -% :::include::end -- do not remove This approach allows for much more concise code, and also avoids importing classes (and even remembering their names) since types are inferred from the method parameter signature. Note in the above example that builder variables are only used to start a chain of property setters. The names of these variables are therefore unimportant and can be shortened to improve readability: - -% :::include::start -- do not remove -```java -ElasticsearchClient client = ... +ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() .create(c -> c .index("my-index") @@ -77,21 +57,14 @@ CreateIndexResponse createResponse = client.indices() ) ); ``` -% :::include::end -- do not remove Builder lambdas become particularly useful with complex nested queries like the one below, taken from the [intervals query API documentation](elasticsearch://reference/query-languages/query-dsl/query-dsl-intervals-query.md). This example also highlights a useful naming convention for builder parameters in deeply nested structures. For lambda expressions with a single argument, Kotlin provides the implicit `it` parameter and Scala allows use of `_`. This can be approximated in Java by using an underscore or a single letter prefix followed by a number representing the depth level (i.e. `_0`, `_1`, or `b0`, `b1` and so on). Not only does this remove the need to create throw-away variable names, but it also improves code readability. Correct indentation also allows the structure of the query to stand out. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-intervals ```java -ElasticsearchClient client = ... +ElasticsearchClient client = createClient(); SearchResponse results = client .search(b0 -> b0 .query(b1 -> b1 @@ -126,7 +99,6 @@ SearchResponse results = client SomeApplicationData.class // <1> ); ``` -% :::include::end -- do not remove 1. Search results will be mapped to `SomeApplicationData` instances to be readily available to the application. diff --git a/docs/reference/api-conventions/lists-maps.md b/docs/reference/api-conventions/lists-maps.md index 9909a588c..3ac7f4ff0 100644 --- a/docs/reference/api-conventions/lists-maps.md +++ b/docs/reference/api-conventions/lists-maps.md @@ -12,12 +12,7 @@ Properties of type `List` and `Map` are exposed by object builders as a set of o Object builders create immutable objects, and this also applies to list and map properties that are made immutable at object construction time. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=collections ```java // Prepare a list of index names List names = Arrays.asList("idx-a", "idx-b", "idx-c"); @@ -53,7 +48,6 @@ SearchRequest search = SearchRequest.of(r -> r a -> a.histogram(h -> h.field("price"))) ); ``` -% :::include::end -- do not remove ## List and map values are never `null` [_list_and_map_values_are_never_null] @@ -63,12 +57,7 @@ For lists and maps however, applications often only care about whether they’re If you ever need to distinguish between a missing (undefined) optional collection and an effectively-empty collection returned by {{es}}, the `ApiTypeHelper` class provides a utility method to distinguish them: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=optional-collections ```java NodeStatistics stats = NodeStatistics.of(b -> b .total(1) @@ -84,7 +73,6 @@ assertEquals(0, stats.failures().size()); // - and if needed we can know it was actually not defined assertFalse(ApiTypeHelper.isDefined(stats.failures())); ``` -% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/api-conventions/loading-json.md b/docs/reference/api-conventions/loading-json.md index a32b27569..7a98d6322 100644 --- a/docs/reference/api-conventions/loading-json.md +++ b/docs/reference/api-conventions/loading-json.md @@ -31,12 +31,7 @@ Consider a resource file `some-index.json` containing an index definition: You can create an index from that definition as follows: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=load-index ```java InputStream input = this.getClass() .getResourceAsStream("some-index.json"); // <1> @@ -48,7 +43,6 @@ CreateIndexRequest req = CreateIndexRequest.of(b -> b boolean created = client.indices().create(req).acknowledged(); ``` -% :::include::end -- do not remove 1. open an input stream for the JSON resource file. 2. populate the index creation request with the resource file contents. @@ -59,12 +53,7 @@ boolean created = client.indices().create(req).acknowledged(); Similarly, you can read documents to be stored in {{es}} from data files: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=ingest-data ```java FileReader file = new FileReader(new File(dataDir, "document-1.json")); @@ -77,7 +66,6 @@ req = IndexRequest.of(b -> b client.index(req); ``` -% :::include::end -- do not remove 1. when calling `withJson()` on data structures that have generic type parameters, these generic types will be considered to be `JsonData`. @@ -87,12 +75,7 @@ client.index(req); You can combine `withJson()` with regular calls to setter methods. The example below loads the query part of a search request from a `String` and programmatically adds an aggregation. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query ```java Reader queryJson = new StringReader( "{" + @@ -123,7 +106,6 @@ Map aggs = client .search(aggRequest, Void.class) // <3> .aggregations(); ``` -% :::include::end -- do not remove 1. loads the query from the JSON string. 2. adds the aggregation. @@ -135,12 +117,7 @@ Map aggs = client The `withJson()` methods are partial deserializers: the properties loaded from the JSON will set property values or replace the previous ones, but will not reset other properties not found in the JSON input. You can use this to combine multiple JSON snippets to build complex search requests. In the example below, we combine separate definitions of a query that selects some documents and an aggregation that is run on the results of this query. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query-and-agg ```java Reader queryJson = new StringReader( "{" + @@ -184,7 +161,6 @@ Map aggs = client .search(aggRequest, Void.class) .aggregations(); ``` -% :::include::end -- do not remove 1. set max number of returned document to 100 for queries. 2. we do not want any matching document in aggregations. diff --git a/docs/reference/api-conventions/variant-types.md b/docs/reference/api-conventions/variant-types.md index 1d58ee527..2f07acbfa 100644 --- a/docs/reference/api-conventions/variant-types.md +++ b/docs/reference/api-conventions/variant-types.md @@ -13,12 +13,7 @@ This is because variant objects in the Java API Client are implementations of a Variant builders have setter methods for every available implementation. They use the same conventions as regular properties and accept both a builder lambda expression and a ready-made object of the actual type of the variant. Here’s an example to build a term query: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-creation ```java Query query = new Query.Builder() .term(t -> t // <1> @@ -27,7 +22,6 @@ Query query = new Query.Builder() ) .build(); // <3> ``` -% :::include::end -- do not remove 1. Choose the `term` variant to build a term query. 2. Build the terms query with a builder lambda expression. @@ -36,16 +30,10 @@ Query query = new Query.Builder() Variant objects have getter methods for every available implementation. These methods check that the object actually holds a variant of that kind and return the value downcasted to the correct type. They throw an `IllegalStateException` otherwise. This approach allows writing fluent code to traverse variants. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-navigation ```java assertEquals("foo", query.term().value().stringValue()); ``` -% :::include::end -- do not remove Variant objects also provide information on the variant kind they currently hold: @@ -54,12 +42,7 @@ Variant objects also provide information on the variant kind they currently hold This information can then be used to navigate down into specific variants after checking their actual kind: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-kind ```java if (query.isTerm()) { // <1> doSomething(query.term()); @@ -76,14 +59,11 @@ switch(query._kind()) { // <2> doSomething(query._kind(), query._get()); // <3> } ``` -% :::include::end -- do not remove 1. Test if the variant is of a specific kind. 2. Test a larger set of variant kinds. 3. Get the kind and value held by the variant object. - - ## Custom extensions provided by {{es}} plugins [variant-types-custom] {{es}} accepts plugins that can extend the available variants for a number of types. This includes queries, aggregations, text analyzers and tokenizers, ingest processors, etc. @@ -94,12 +74,7 @@ In the examples below we use a hypothetical plugin that adds a `sphere-distance` To create a custom aggregation, use the `_custom()` aggregation type and provide its identifier, defined by the plugin, and parameters. The parameters can be any object or value that can be serialized to JSON. In the example below we use a simple map: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-creation ```java Map params = new HashMap<>(); // <1> params.put("interval", 10); @@ -113,7 +88,6 @@ SearchRequest request = SearchRequest.of(r -> r ) ); ``` -% :::include::end -- do not remove 1. Parameters for the custom aggregation. 2. Create a custom aggregation named `neighbors` of kind `sphere-distance` with its parameters. @@ -123,12 +97,7 @@ The results of custom variants are returned as raw JSON represented by a `JsonDa Traversing the JSON tree: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-json ```java SearchResponse response = esClient.search(request, Void.class); // <1> @@ -147,7 +116,6 @@ for (JsonValue item : buckets) { doSomething(key, docCount); } ``` -% :::include::end -- do not remove 1. Use `Void` if you’re only interested in aggregation results, not search hits (see also [Aggregations](/reference/usage/aggregations.md)). 2. Get the `neighbors` aggregation result as custom JSON result. @@ -156,12 +124,7 @@ for (JsonValue item : buckets) { Using a class that represents the custom aggregation results: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-typed ```java SearchResponse response = esClient.search(request, Void.class); @@ -174,19 +137,13 @@ for (Bucket bucket : neighbors.buckets()) { doSomething(bucket.key(), bucket.docCount()); } ``` -% :::include::end -- do not remove 1. Deserialize the custom JSON to a dedicated `SphereDistanceAggregate` class. Where `SphereDistanceAggregate` can be defined as follows: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-types ```java public static class SphereDistanceAggregate { private final List buckets; @@ -219,7 +176,6 @@ public static class Bucket { } } ``` -% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/getting-started.md b/docs/reference/getting-started.md index b12167693..1603f17d1 100644 --- a/docs/reference/getting-started.md +++ b/docs/reference/getting-started.md @@ -51,12 +51,7 @@ Refer to the [Installation](setup/installation.md) page to learn more. You can connect to the Elastic Cloud using an API key and the Elasticsearch endpoint. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client ```java // URL and API key String serverUrl = "https://localhost:9200"; @@ -74,7 +69,6 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` -% :::include::end -- do not remove Your Elasticsearch endpoint can be found on the **My deployment** page of your deployment: @@ -100,30 +94,19 @@ Time to use Elasticsearch! This section walks you through the basic, and most im This is how you create the `product` index: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=create-products-index ```java esClient.indices().create(c -> c .index("products") ); ``` -% :::include::end -- do not remove #### Indexing documents [_indexing_documents] This is a simple way of indexing a document, here a `Product` application object: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -135,19 +118,12 @@ IndexResponse response = esClient.index(i -> i logger.info("Indexed with version " + response.version()); ``` -% :::include::end -- do not remove - #### Getting documents [_getting_documents] You can get documents by using the following code: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id ```java GetResponse response = esClient.get(g -> g .index("products") // <1> @@ -162,23 +138,15 @@ if (response.found()) { logger.info ("Product not found"); } ``` -% :::include::end -- do not remove 1. The get request, with the index name and identifier. 2. The target class, here `Product`. - - #### Searching documents [_searching_documents] This is how you can create a single match query with the Java client: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-getting-started ```java String searchText = "bike"; @@ -193,19 +161,12 @@ SearchResponse response = esClient.search(s -> s Product.class ); ``` -% :::include::end -- do not remove - #### Updating documents [_updating_documents] This is how you can update a document, for example to add a new field: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-update ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -216,38 +177,22 @@ esClient.update(u -> u Product.class ); ``` -% :::include::end -- do not remove - #### Deleting documents [_deleting_documents] - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-delete ```java esClient.delete(d -> d.index("products").id("bk-1")); ``` -% :::include::end -- do not remove - #### Deleting an index [_deleting_an_index] - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=delete-products-index ```java esClient.indices().delete(d -> d .index("products") ); ``` -% :::include::end -- do not remove - ## Examples [_examples] diff --git a/docs/reference/setup/connecting.md b/docs/reference/setup/connecting.md index 0323f6769..4676dd40d 100644 --- a/docs/reference/setup/connecting.md +++ b/docs/reference/setup/connecting.md @@ -13,12 +13,7 @@ The Java API Client is structured around three main components: This code snippet creates and wires together these three components: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client ```java // URL and API key String serverUrl = "https://localhost:9200"; @@ -36,7 +31,6 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` -% :::include::end -- do not remove Authentication is managed by the [Java Low Level REST Client](/reference/transport/rest-client/index.md). For further details on configuring authentication, refer to [its documentation](/reference/transport/rest-client/config/basic_authentication.md). @@ -47,12 +41,7 @@ The code snippet below searches all items from a “product” index whose name It illustrates the use of fluent functional builders to write search queries as concise DSL-like code. This pattern is explained in more detail in [*API conventions*](/reference/api-conventions/index.md). - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=first-request ```java SearchResponse search = esClient.search(s -> s .index("products") @@ -67,7 +56,6 @@ for (Hit hit: search.hits().hits()) { processProduct(hit.source()); } ``` -% :::include::end -- do not remove ## Using a secure connection [using-a-secure-connection] @@ -98,12 +86,7 @@ Depending on the context, you have two options for verifying the HTTPS connectio This method of verifying the HTTPS connection uses the certificate fingerprint value noted down earlier. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-fingerprint ```java String fingerprint = ""; @@ -121,7 +104,6 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` -% :::include::end -- do not remove 1. Create an `SSLContext` with the certificate fingerprint. 2. Set up authentication. @@ -149,12 +131,7 @@ The generated root CA certificate can be found in the `certs` directory in your Once you have made the `http_ca.crt` file available to your application, you can use it to set up the client: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-cert ```java File certFile = new File("/path/to/http_ca.crt"); @@ -172,7 +149,6 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` -% :::include::end -- do not remove 1. Create an `SSLContext` with the `http_ca.crt` file. 2. Set up authentication. diff --git a/docs/reference/setup/opentelemetry.md b/docs/reference/setup/opentelemetry.md index 6f5ad6fde..80626905e 100644 --- a/docs/reference/setup/opentelemetry.md +++ b/docs/reference/setup/opentelemetry.md @@ -40,12 +40,7 @@ When using the [OpenTelemetry Java SDK manually](https://opentelemetry.io/docs/i In case you are using [manual OpenTelemetry instrumentation](https://opentelemetry.io/docs/instrumentation/java/manual/#example) with a custom OpenTelemetry SDK instance that is *not registered globally*, you can create the Java API Client using a custom OpenTelemetry instance. The following code snippet shows an example of using a custom OpenTelemetry instance. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client-otel ```java // URL and API key String serverUrl = "https://localhost:9200"; @@ -70,8 +65,6 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b // Close the client, also closing the underlying transport object and network connections. esClient.close(); ``` -% :::include::end -- do not remove - ## Configuring the OpenTelemetry instrumentation [_configuring_the_opentelemetry_instrumentation] diff --git a/docs/reference/usage/aggregations.md b/docs/reference/usage/aggregations.md index afc4c6154..0ac96cda2 100644 --- a/docs/reference/usage/aggregations.md +++ b/docs/reference/usage/aggregations.md @@ -21,12 +21,7 @@ This example is an analytics-type aggregation where we do not want to use the ma If that same aggregation was used for to display products and the price histogram as drill-down facets, we would have set `size` to a non-zero value and used `Product` as the target class to process the results. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-request ```java String searchText = "bike"; @@ -48,7 +43,6 @@ SearchResponse response = esClient.search(b -> b Void.class // <5> ); ``` -% :::include::end -- do not remove 1. Set the number of matching documents to zero as we only use the price histogram. 2. Set the query that fill filter the products on which to run the aggregation @@ -59,12 +53,7 @@ SearchResponse response = esClient.search(b -> b The response contains an aggregation result for each aggregation in the request. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-response ```java List buckets = response.aggregations() .get("price-histogram") // <1> @@ -77,7 +66,6 @@ for (HistogramBucket bucket: buckets) { } ``` -% :::include::end -- do not remove 1. Get the results for the "price-histogram" aggregation. 2. Cast it down to the `histogram` variant results. This has to be consistent with the aggregation definition. diff --git a/docs/reference/usage/indexing-bulk.md b/docs/reference/usage/indexing-bulk.md index dd2a4db05..6fd8d6ed2 100644 --- a/docs/reference/usage/indexing-bulk.md +++ b/docs/reference/usage/indexing-bulk.md @@ -18,20 +18,13 @@ A bulk request can contain several kinds of operations: See the [{{es}} API documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk) for a full explanation of bulk requests. :::: - - ## Indexing application objects [_indexing_application_objects] A `BulkRequest` contains a collection of operations, each operation being a [type with several variants](/reference/api-conventions/variant-types.md). To create this request, it is convenient to use a builder object for the main request, and the fluent DSL for each operation. The example below shows how to index a list or application objects. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-objects ```java List products = fetchProducts(); @@ -59,26 +52,18 @@ if (result.errors()) { } } ``` -% :::include::end -- do not remove 1. Adds an operation (remember that [list properties are additive](/reference/api-conventions/lists-maps.md)). `op` is is a builder for `BulkOperation` which is a [variant type](/reference/api-conventions/variant-types.md). This type has `index`, `create`, `update` and `delete` variants. 2. Selects the `index` operation variant, `idx` is a builder for `IndexOperation`. 3. Sets the properties for the index operation, similar to [single document indexing](indexing.md): index name, identifier and document. - - ## Indexing raw JSON data [indexing-raw-json-data] The `document` property of a bulk index request can be any object that can be serialized to JSON using your Elasticsearch client’s JSON mapper. However, data that is ingested in bulk is often available as JSON text (e.g. files on disk), and parsing this JSON just to re-serialize it to send the bulk request would be a waste of resources. So documents in bulk operations can also be of type `BinaryData` that are sent verbatim (without parsing) to the {{es}} server. In the example below we will use the Java API Client’s `BinaryData` to read json files from a log directory and send them in a bulk request. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-json ```java // List json log files in the log directory File[] logFiles = logDir.listFiles( @@ -99,8 +84,6 @@ for (File file: logFiles) { ); } ``` -% :::include::end -- do not remove - ## Streaming ingestion with the Bulk Ingester [_streaming_ingestion_with_the_bulk_ingester] @@ -114,12 +97,7 @@ The ingester will send a bulk request when one of the following criteria is met: Additionally, you can define a maximum number of concurrent request waiting to be executed by {{es}} (defaults to 1). When that maximum is reached and the maximum number of operations have been collected, adding a new operation to the indexer will block. This is avoids overloading the {{es}} server by putting backpressure on the client application. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-setup ```java BulkIngester ingester = BulkIngester.of(b -> b .client(esClient) // <1> @@ -141,7 +119,6 @@ for (File file: logFiles) { ingester.close(); // <5> ``` -% :::include::end -- do not remove 1. Sets the {{es}} client used to send bulk requests. 2. Sets the maximum number of operations to collect before sending a bulk request. @@ -154,12 +131,7 @@ Additionally, the bulk ingester accepts a listener so that your application can The following example shows how you can use context values to implement a bulk ingestion listener: as previously it sends JSON log files in bulk, but tracks bulk request errors and failed operations. When an operation fails, depending on the error type you may want to re-add it to the ingester. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-context ```java BulkListener listener = new BulkListener() { // <1> @Override @@ -209,7 +181,6 @@ for (File file: logFiles) { ingester.close(); ``` -% :::include::end -- do not remove 1. Creates a listener where context values are strings for the ingested file name. 2. Registers the listener on the bulk ingester. diff --git a/docs/reference/usage/indexing.md b/docs/reference/usage/indexing.md index 92b105bbc..a15254c3a 100644 --- a/docs/reference/usage/indexing.md +++ b/docs/reference/usage/indexing.md @@ -19,12 +19,7 @@ See the [{{es}} API documentation](https://www.elastic.co/docs/api/doc/elasticse The most direct way to build requests is using the fluent DSL. In the example below we index a product description in the `products` index, using the product’s SKU as the document identifier in the index. The `product` object will be mapped to JSON using the object mapper configured on the {{es}} client. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -36,16 +31,10 @@ IndexResponse response = esClient.index(i -> i logger.info("Indexed with version " + response.version()); ``` -% :::include::end -- do not remove You can also assign objects created with the DSL to variables. Java API Client classes have a static `of()` method for this, that creates an object with the DSL syntax. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-of ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -59,18 +48,12 @@ IndexResponse response = esClient.index(request); logger.info("Indexed with version " + response.version()); ``` -% :::include::end -- do not remove ## Using classic builders [_using_classic_builders] If you’re more used to the classic builder pattern, it is also available. Builder objects are used under the hood by the fluent DSL syntax. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-builder ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -83,19 +66,12 @@ IndexResponse response = esClient.index(indexReqBuilder.build()); logger.info("Indexed with version " + response.version()); ``` -% :::include::end -- do not remove - ## Using the asynchronous client [_using_the_asynchronous_client] The examples above used the synchronous {{es}} client. All {{es}} APIs are also available in the asynchronous client, using the same request and response types. See also [Blocking and asynchronous clients](/reference/api-conventions/blocking-async.md) for additional details. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-async ```java ElasticsearchAsyncClient esAsyncClient = new ElasticsearchAsyncClient(transport); @@ -113,8 +89,6 @@ esAsyncClient.index(i -> i } }); ``` -% :::include::end -- do not remove - ## Using raw JSON data [_using_raw_json_data] @@ -122,12 +96,7 @@ When the data you want to index comes from external sources, having to create do You can index data from an arbitrary source using `withJson()`. Using this method will read the source and use it for the index request’s `document` property. See [Creating API objects from JSON data](/reference/api-conventions/loading-json.md) for additional details. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-json ```java Reader input = new StringReader( "{'@timestamp': '2022-04-08T13:55:32Z', 'level': 'warn', 'message': 'Some log message'}" @@ -142,7 +111,6 @@ IndexResponse response = esClient.index(request); logger.info("Indexed with version " + response.version()); ``` -% :::include::end -- do not remove :::{include} /reference/_snippets/doc-tests-blurb.md ::: diff --git a/docs/reference/usage/reading.md b/docs/reference/usage/reading.md index b9b711ff4..f49f39f65 100644 --- a/docs/reference/usage/reading.md +++ b/docs/reference/usage/reading.md @@ -22,12 +22,7 @@ The `get` request has two parameters: * the first parameter is the actual request, built below with the fluent DSL * the second parameter is the class we want the document’s JSON to be mapped to. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id ```java GetResponse response = esClient.get(g -> g .index("products") // <1> @@ -42,7 +37,6 @@ if (response.found()) { logger.info ("Product not found"); } ``` -% :::include::end -- do not remove 1. The get request, with the index name and identifier. 2. The target class, here `Product`. @@ -55,12 +49,7 @@ When your index contains semi-structured data or if you don’t have a domain ob Raw JSON data is just another class that you can use as the result type for the get request. In the example below we use Jackson’s `ObjectNode`. We could also have used any JSON representation that can be deserialized by the JSON mapper associated to the `ElasticsearchClient`. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id-json ```java GetResponse response = esClient.get(g -> g .index("products") @@ -76,7 +65,6 @@ if (response.found()) { logger.info("Product not found"); } ``` -% :::include::end -- do not remove 1. The target class is a raw JSON object. diff --git a/docs/reference/usage/searching.md b/docs/reference/usage/searching.md index 810d02cb0..0d3363368 100644 --- a/docs/reference/usage/searching.md +++ b/docs/reference/usage/searching.md @@ -23,12 +23,7 @@ The total value comes with a relation that indicates if the total is exact (`eq` Each returned document comes with its relevance score and additional information about its location in the index. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-simple ```java String searchText = "bike"; @@ -59,7 +54,6 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` -% :::include::end -- do not remove 1. Name of the index we want to search. 2. The query part of the search request (a search request can also have other components like [aggregations](aggregations.md)). @@ -75,12 +69,7 @@ Similarly to [get](reading.md) operations, you can fetch documents matching your {{es}} allows individual queries to be combined to build more complex search requests. In the example below we will search for bikes with a maximum price of 200. - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-nested ```java String searchText = "bike"; double maxPrice = 200.0; @@ -116,7 +105,6 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` -% :::include::end -- do not remove 1. We’re creating the queries for individual criteria separately. 2. A `MatchQuery` is a query *variant* that we have to turn into the `Query` *union type*. See [variant types](/reference/api-conventions/variant-types.md) for additional details. @@ -132,12 +120,7 @@ A search template is a stored search that you can run with different variables. Before running a template search, you first have to create the template. This is a stored script that returns the search request body, and is usually defined as a Mustache template. This stored script can be created outside the application, and also with the Java API Client: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-script ```java // Create a script esClient.putScript(r -> r @@ -147,19 +130,13 @@ esClient.putScript(r -> r .source(so -> so.scriptString("{\"query\":{\"match\":{\"{{field}}\":\"{{value}}\"}}}")) )); ``` -% :::include::end -- do not remove 1. Identifier of the template script to create. To use the search template, use the `searchTemplate` method to refer to the script and provide values for its parameters: - -% :::include::start -- do not remove +% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-query ```java SearchTemplateResponse response = esClient.searchTemplate(r -> r .index("some-index") @@ -175,7 +152,6 @@ for (Hit hit: hits) { logger.info("Found product " + product.getSku() + ", score " + hit.score()); } ``` -% :::include::end -- do not remove 1. Identifier of the template script to use. 2. Template parameter values. diff --git a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java index 17ac641c6..8e3ad9dee 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java @@ -85,10 +85,14 @@ public void blockingAndAsync() throws Exception { } + private ElasticsearchClient createClient() { + return new ElasticsearchClient(transport); + } + public void builders() throws Exception { - ElasticsearchClient client = new ElasticsearchClient(transport); //tag::builders + ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices().create( new CreateIndexRequest.Builder() .index("my-index") @@ -101,9 +105,9 @@ public void builders() throws Exception { } public void builderLambdas() throws Exception { - ElasticsearchClient client = new ElasticsearchClient(transport); //tag::builder-lambdas + ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() .create(createIndexBuilder -> createIndexBuilder .index("my-index") @@ -115,9 +119,9 @@ public void builderLambdas() throws Exception { } public void builderLambdasShort() throws Exception { - ElasticsearchClient client = new ElasticsearchClient(transport); //tag::builder-lambdas-short + ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() .create(c -> c .index("my-index") @@ -129,11 +133,8 @@ public void builderLambdasShort() throws Exception { } public void builderIntervals() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - ElasticsearchClient client = new ElasticsearchClient(transport); - //tag::builder-intervals + ElasticsearchClient client = createClient(); SearchResponse results = client .search(b0 -> b0 .query(b1 -> b1 diff --git a/tools/build.gradle.kts b/tools/build.gradle.kts index ec22c992f..c758a5622 100644 --- a/tools/build.gradle.kts +++ b/tools/build.gradle.kts @@ -38,10 +38,11 @@ dependencies { } tasks.register("expand-includes") { + workingDir = rootDir group = "application" mainClass.set("co.elastic.clients.tools.docs.IncludeExpander") args = listOf( - "../docs/reference", + "docs/reference", ) classpath = sourceSets["main"].runtimeClasspath } diff --git a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java index 067bea89f..6ff9f6026 100644 --- a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java +++ b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java @@ -26,25 +26,26 @@ import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Objects; public class IncludeExpander { public static void main(String[] args) throws IOException { - File dir = new File(args[0]); + + File dir = new File(args.length == 0 ? "docs/reference" : args[0]); if (!dir.isDirectory()) { throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory"); } - processDirectory(dir, Map.of("doc-tests-src", "../java-client/src/test/java/co/elastic/clients/documentation")); + processDirectory(dir, Map.of("doc-tests-src", "java-client/src/test/java/co/elastic/clients/documentation")); } public static void processDirectory(File dir, Map subst) throws IOException { - System.out.println("Processing directory " + dir); + //System.out.println("Processing directory " + dir); // Traverse all files for (File file : dir.listFiles()) { if (file.isDirectory()) { @@ -78,8 +79,8 @@ public static void processFile(File file, Map subst) throws IOEx enum State { NORMAL_TEXT, - INCLUDE_DIRECTIVE, - INCLUDE_EXPANSION, + INCLUDE_CODE, + CODE_BLOCK, } public static void fail(String message, String path, LineNumberReader reader, Throwable e) { @@ -87,8 +88,7 @@ public static void fail(String message, String path, LineNumberReader reader, Th } public static String expandText(String input, Map subst, String path) throws IOException { - int start = input.indexOf("")) { - // End of template: append expanded output with enclosing markers - output.append("% :::include::start -- do not remove").append("\n"); - output.append(expanded); - output.append("% :::include::end -- do not remove").append("\n"); + case CODE_BLOCK -> { + // Skip existing code until we reach the end + if (line.startsWith("```")) { + output.append(line).append("\n"); state = State.NORMAL_TEXT; - - } else { - // Regular line in the include template - expanded.append(line).append("\n"); } } } @@ -160,26 +145,23 @@ public static String expandText(String input, Map subst, String return output.toString(); } - // Extracts path and tag from ":::include some/path/to/Source.java[tag-name]" - public static Pattern DIRECTIVE = Pattern.compile(":::\\{include}\\s+([^\\[]+)\\[([^]]*).*"); - - public static void expandIncludeDirective(String input, Map subst, StringBuilder output) throws IOException { - - Matcher matcher = DIRECTIVE.matcher(input); - - if (!matcher.matches()) { - throw new RuntimeException("Invalid directive: " + input); + public static void expandIncludeCodeDirective(String command, Map subst, StringBuilder output) throws IOException { + String[] s = command.split(" "); + Map args = new HashMap<>(); + for (int i = 2; i < s.length; i++) { + var kv = s[i].split("="); + args.put(kv[0], kv[1]); } - String path = matcher.group(1); - String tag = matcher.group(2); + var src = Objects.requireNonNull(args.get("src"), "Missing 'src' attribute"); + var tag = Objects.requireNonNull(args.get("tag"), "Missing 'tag' attribute"); // Brute force replacement of placeholders for (var kv: subst.entrySet()) { - path = path.replace("{" + kv.getKey() + "}", kv.getValue()); + src = src.replace("{{" + kv.getKey() + "}}", kv.getValue()); } - expandTaggedFile(path, tag, output); + expandTaggedFile(src, tag, output); } public static void expandTaggedFile(String path, String tag, StringBuilder output) throws IOException { @@ -216,49 +198,4 @@ public static void expandTaggedFile(String path, String tag, StringBuilder outpu throw new RuntimeException("Missing start tag '" + tag + "' in " + path); } } - - // TODO: write proper tests - public static void main0(String[] args) throws IOException { - var string = ":::{include} {doc-tests-src}/api_conventions/ApiConventionsTest.java[blocking-and-async]"; - - Matcher matcher = DIRECTIVE.matcher(string); - if (matcher.matches()) { - for (int i = 1; i <= matcher.groupCount(); i++) { - System.out.println(i + " - " + matcher.group(i)); - } - } else { - System.out.println("No match found"); - } - - } - - - public static void main1(String[] args) throws IOException { - - System.out.println(expandText(""" - Text before - - - % :::include::start -- do not remove - - ```java - ElasticsearchTransport transport = ... - - ``` - - % :::include::end -- do not remove - - Text after - """, - Map.of("doc-tests-src", "java-client/src/test/java/co/elastic/clients/documentation"), - "some-path" - )); - - } } From 266c90246af3e58a81ff2f193724fa4171931996 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 07:37:51 +0200 Subject: [PATCH 06/14] Use a more mystmd-y syntax --- docs/reference/api-conventions/blocking-async.md | 2 +- .../api-conventions/building-objects.md | 8 ++++---- docs/reference/api-conventions/lists-maps.md | 4 ++-- docs/reference/api-conventions/loading-json.md | 8 ++++---- docs/reference/api-conventions/variant-types.md | 14 +++++++------- docs/reference/getting-started.md | 16 ++++++++-------- docs/reference/setup/connecting.md | 8 ++++---- docs/reference/setup/opentelemetry.md | 2 +- docs/reference/usage/aggregations.md | 4 ++-- docs/reference/usage/indexing-bulk.md | 8 ++++---- docs/reference/usage/indexing.md | 10 +++++----- docs/reference/usage/reading.md | 4 ++-- docs/reference/usage/searching.md | 8 ++++---- .../clients/tools/docs/IncludeExpander.java | 6 +++--- 14 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/reference/api-conventions/blocking-async.md b/docs/reference/api-conventions/blocking-async.md index bdea495b0..3d1a04c26 100644 --- a/docs/reference/api-conventions/blocking-async.md +++ b/docs/reference/api-conventions/blocking-async.md @@ -9,7 +9,7 @@ API clients come in two flavors: blocking and asynchronous. All methods on async Both flavors can be used at the same time depending on your needs, sharing the same transport object: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=blocking-and-async +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=blocking-and-async ```java // Synchronous blocking client ElasticsearchClient client = new ElasticsearchClient(transport); diff --git a/docs/reference/api-conventions/building-objects.md b/docs/reference/api-conventions/building-objects.md index 8a2c4f6ba..a9cf2e4bb 100644 --- a/docs/reference/api-conventions/building-objects.md +++ b/docs/reference/api-conventions/building-objects.md @@ -10,7 +10,7 @@ mapped_pages: All data types in the Java API Client are immutable. Object creation uses the [builder pattern](https://www.informit.com/articles/article.aspx?p=1216151&seqNum=2) that was popularized in **Effective Java** in 2008. -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builders +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builders ```java ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices().create( @@ -30,7 +30,7 @@ Note that a builder should not be reused after its `build()` method has been cal Although this works nicely, having to instantiate builder classes and call the `build()` method is a bit verbose. So every property setter in the Java API Client also accepts a lambda expression that takes a newly created builder as a parameter and returns a populated builder. The snippet above can also be written as: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas ```java ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() @@ -46,7 +46,7 @@ This approach allows for much more concise code, and also avoids importing class Note in the above example that builder variables are only used to start a chain of property setters. The names of these variables are therefore unimportant and can be shortened to improve readability: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas-short +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas-short ```java ElasticsearchClient client = createClient(); CreateIndexResponse createResponse = client.indices() @@ -62,7 +62,7 @@ Builder lambdas become particularly useful with complex nested queries like the This example also highlights a useful naming convention for builder parameters in deeply nested structures. For lambda expressions with a single argument, Kotlin provides the implicit `it` parameter and Scala allows use of `_`. This can be approximated in Java by using an underscore or a single letter prefix followed by a number representing the depth level (i.e. `_0`, `_1`, or `b0`, `b1` and so on). Not only does this remove the need to create throw-away variable names, but it also improves code readability. Correct indentation also allows the structure of the query to stand out. -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-intervals +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-intervals ```java ElasticsearchClient client = createClient(); SearchResponse results = client diff --git a/docs/reference/api-conventions/lists-maps.md b/docs/reference/api-conventions/lists-maps.md index 3ac7f4ff0..a5e891841 100644 --- a/docs/reference/api-conventions/lists-maps.md +++ b/docs/reference/api-conventions/lists-maps.md @@ -12,7 +12,7 @@ Properties of type `List` and `Map` are exposed by object builders as a set of o Object builders create immutable objects, and this also applies to list and map properties that are made immutable at object construction time. -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=collections +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=collections ```java // Prepare a list of index names List names = Arrays.asList("idx-a", "idx-b", "idx-c"); @@ -57,7 +57,7 @@ For lists and maps however, applications often only care about whether they’re If you ever need to distinguish between a missing (undefined) optional collection and an effectively-empty collection returned by {{es}}, the `ApiTypeHelper` class provides a utility method to distinguish them: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=optional-collections +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=optional-collections ```java NodeStatistics stats = NodeStatistics.of(b -> b .total(1) diff --git a/docs/reference/api-conventions/loading-json.md b/docs/reference/api-conventions/loading-json.md index 7a98d6322..08e7a677d 100644 --- a/docs/reference/api-conventions/loading-json.md +++ b/docs/reference/api-conventions/loading-json.md @@ -31,7 +31,7 @@ Consider a resource file `some-index.json` containing an index definition: You can create an index from that definition as follows: -% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=load-index +% :::{include-code} src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=load-index ```java InputStream input = this.getClass() .getResourceAsStream("some-index.json"); // <1> @@ -53,7 +53,7 @@ boolean created = client.indices().create(req).acknowledged(); Similarly, you can read documents to be stored in {{es}} from data files: -% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=ingest-data +% :::{include-code} src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=ingest-data ```java FileReader file = new FileReader(new File(dataDir, "document-1.json")); @@ -75,7 +75,7 @@ client.index(req); You can combine `withJson()` with regular calls to setter methods. The example below loads the query part of a search request from a `String` and programmatically adds an aggregation. -% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query +% :::{include-code} src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query ```java Reader queryJson = new StringReader( "{" + @@ -117,7 +117,7 @@ Map aggs = client The `withJson()` methods are partial deserializers: the properties loaded from the JSON will set property values or replace the previous ones, but will not reset other properties not found in the JSON input. You can use this to combine multiple JSON snippets to build complex search requests. In the example below, we combine separate definitions of a query that selects some documents and an aggregation that is run on the results of this query. -% :::include-code src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query-and-agg +% :::{include-code} src={{doc-tests-src}}/api_conventions/LoadingJsonTest.java tag=query-and-agg ```java Reader queryJson = new StringReader( "{" + diff --git a/docs/reference/api-conventions/variant-types.md b/docs/reference/api-conventions/variant-types.md index 2f07acbfa..170eab438 100644 --- a/docs/reference/api-conventions/variant-types.md +++ b/docs/reference/api-conventions/variant-types.md @@ -13,7 +13,7 @@ This is because variant objects in the Java API Client are implementations of a Variant builders have setter methods for every available implementation. They use the same conventions as regular properties and accept both a builder lambda expression and a ready-made object of the actual type of the variant. Here’s an example to build a term query: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-creation +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-creation ```java Query query = new Query.Builder() .term(t -> t // <1> @@ -30,7 +30,7 @@ Query query = new Query.Builder() Variant objects have getter methods for every available implementation. These methods check that the object actually holds a variant of that kind and return the value downcasted to the correct type. They throw an `IllegalStateException` otherwise. This approach allows writing fluent code to traverse variants. -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-navigation +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-navigation ```java assertEquals("foo", query.term().value().stringValue()); ``` @@ -42,7 +42,7 @@ Variant objects also provide information on the variant kind they currently hold This information can then be used to navigate down into specific variants after checking their actual kind: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-kind +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=variant-kind ```java if (query.isTerm()) { // <1> doSomething(query.term()); @@ -74,7 +74,7 @@ In the examples below we use a hypothetical plugin that adds a `sphere-distance` To create a custom aggregation, use the `_custom()` aggregation type and provide its identifier, defined by the plugin, and parameters. The parameters can be any object or value that can be serialized to JSON. In the example below we use a simple map: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-creation +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-creation ```java Map params = new HashMap<>(); // <1> params.put("interval", 10); @@ -97,7 +97,7 @@ The results of custom variants are returned as raw JSON represented by a `JsonDa Traversing the JSON tree: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-json +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-json ```java SearchResponse response = esClient.search(request, Void.class); // <1> @@ -124,7 +124,7 @@ for (JsonValue item : buckets) { Using a class that represents the custom aggregation results: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-typed +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-navigation-typed ```java SearchResponse response = esClient.search(request, Void.class); @@ -143,7 +143,7 @@ for (Bucket bucket : neighbors.buckets()) { Where `SphereDistanceAggregate` can be defined as follows: -% :::include-code src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-types +% :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=custom-variant-types ```java public static class SphereDistanceAggregate { private final List buckets; diff --git a/docs/reference/getting-started.md b/docs/reference/getting-started.md index 1603f17d1..66ce28451 100644 --- a/docs/reference/getting-started.md +++ b/docs/reference/getting-started.md @@ -51,7 +51,7 @@ Refer to the [Installation](setup/installation.md) page to learn more. You can connect to the Elastic Cloud using an API key and the Elasticsearch endpoint. -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client ```java // URL and API key String serverUrl = "https://localhost:9200"; @@ -94,7 +94,7 @@ Time to use Elasticsearch! This section walks you through the basic, and most im This is how you create the `product` index: -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=create-products-index +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=create-products-index ```java esClient.indices().create(c -> c .index("products") @@ -106,7 +106,7 @@ esClient.indices().create(c -> c This is a simple way of indexing a document, here a `Product` application object: -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -123,7 +123,7 @@ logger.info("Indexed with version " + response.version()); You can get documents by using the following code: -% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id +% :::{include-code} src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id ```java GetResponse response = esClient.get(g -> g .index("products") // <1> @@ -146,7 +146,7 @@ if (response.found()) { This is how you can create a single match query with the Java client: -% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-getting-started +% :::{include-code} src={{doc-tests-src}}/usage/SearchingTest.java tag=search-getting-started ```java String searchText = "bike"; @@ -166,7 +166,7 @@ SearchResponse response = esClient.search(s -> s This is how you can update a document, for example to add a new field: -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-update +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-update ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -180,14 +180,14 @@ esClient.update(u -> u #### Deleting documents [_deleting_documents] -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-delete +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-delete ```java esClient.delete(d -> d.index("products").id("bk-1")); ``` #### Deleting an index [_deleting_an_index] -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=delete-products-index +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=delete-products-index ```java esClient.indices().delete(d -> d .index("products") diff --git a/docs/reference/setup/connecting.md b/docs/reference/setup/connecting.md index 4676dd40d..5b6be8b35 100644 --- a/docs/reference/setup/connecting.md +++ b/docs/reference/setup/connecting.md @@ -13,7 +13,7 @@ The Java API Client is structured around three main components: This code snippet creates and wires together these three components: -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client ```java // URL and API key String serverUrl = "https://localhost:9200"; @@ -41,7 +41,7 @@ The code snippet below searches all items from a “product” index whose name It illustrates the use of fluent functional builders to write search queries as concise DSL-like code. This pattern is explained in more detail in [*API conventions*](/reference/api-conventions/index.md). -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=first-request +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=first-request ```java SearchResponse search = esClient.search(s -> s .index("products") @@ -86,7 +86,7 @@ Depending on the context, you have two options for verifying the HTTPS connectio This method of verifying the HTTPS connection uses the certificate fingerprint value noted down earlier. -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-fingerprint +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-fingerprint ```java String fingerprint = ""; @@ -131,7 +131,7 @@ The generated root CA certificate can be found in the `certs` directory in your Once you have made the `http_ca.crt` file available to your application, you can use it to set up the client: -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-cert +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-secure-client-cert ```java File certFile = new File("/path/to/http_ca.crt"); diff --git a/docs/reference/setup/opentelemetry.md b/docs/reference/setup/opentelemetry.md index 80626905e..0a528ae61 100644 --- a/docs/reference/setup/opentelemetry.md +++ b/docs/reference/setup/opentelemetry.md @@ -40,7 +40,7 @@ When using the [OpenTelemetry Java SDK manually](https://opentelemetry.io/docs/i In case you are using [manual OpenTelemetry instrumentation](https://opentelemetry.io/docs/instrumentation/java/manual/#example) with a custom OpenTelemetry SDK instance that is *not registered globally*, you can create the Java API Client using a custom OpenTelemetry instance. The following code snippet shows an example of using a custom OpenTelemetry instance. -% :::include-code src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client-otel +% :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client-otel ```java // URL and API key String serverUrl = "https://localhost:9200"; diff --git a/docs/reference/usage/aggregations.md b/docs/reference/usage/aggregations.md index 0ac96cda2..269463c1d 100644 --- a/docs/reference/usage/aggregations.md +++ b/docs/reference/usage/aggregations.md @@ -21,7 +21,7 @@ This example is an analytics-type aggregation where we do not want to use the ma If that same aggregation was used for to display products and the price histogram as drill-down facets, we would have set `size` to a non-zero value and used `Product` as the target class to process the results. -% :::include-code src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-request +% :::{include-code} src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-request ```java String searchText = "bike"; @@ -53,7 +53,7 @@ SearchResponse response = esClient.search(b -> b The response contains an aggregation result for each aggregation in the request. -% :::include-code src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-response +% :::{include-code} src={{doc-tests-src}}/usage/AggregationsTest.java tag=price-histo-response ```java List buckets = response.aggregations() .get("price-histogram") // <1> diff --git a/docs/reference/usage/indexing-bulk.md b/docs/reference/usage/indexing-bulk.md index 6fd8d6ed2..1b40cf227 100644 --- a/docs/reference/usage/indexing-bulk.md +++ b/docs/reference/usage/indexing-bulk.md @@ -24,7 +24,7 @@ A `BulkRequest` contains a collection of operations, each operation being a [typ The example below shows how to index a list or application objects. -% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-objects +% :::{include-code} src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-objects ```java List products = fetchProducts(); @@ -63,7 +63,7 @@ The `document` property of a bulk index request can be any object that can be se In the example below we will use the Java API Client’s `BinaryData` to read json files from a log directory and send them in a bulk request. -% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-json +% :::{include-code} src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-json ```java // List json log files in the log directory File[] logFiles = logDir.listFiles( @@ -97,7 +97,7 @@ The ingester will send a bulk request when one of the following criteria is met: Additionally, you can define a maximum number of concurrent request waiting to be executed by {{es}} (defaults to 1). When that maximum is reached and the maximum number of operations have been collected, adding a new operation to the indexer will block. This is avoids overloading the {{es}} server by putting backpressure on the client application. -% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-setup +% :::{include-code} src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-setup ```java BulkIngester ingester = BulkIngester.of(b -> b .client(esClient) // <1> @@ -131,7 +131,7 @@ Additionally, the bulk ingester accepts a listener so that your application can The following example shows how you can use context values to implement a bulk ingestion listener: as previously it sends JSON log files in bulk, but tracks bulk request errors and failed operations. When an operation fails, depending on the error type you may want to re-add it to the ingester. -% :::include-code src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-context +% :::{include-code} src={{doc-tests-src}}/usage/IndexingBulkTest.java tag=bulk-ingester-context ```java BulkListener listener = new BulkListener() { // <1> @Override diff --git a/docs/reference/usage/indexing.md b/docs/reference/usage/indexing.md index a15254c3a..3a4b114c6 100644 --- a/docs/reference/usage/indexing.md +++ b/docs/reference/usage/indexing.md @@ -19,7 +19,7 @@ See the [{{es}} API documentation](https://www.elastic.co/docs/api/doc/elasticse The most direct way to build requests is using the fluent DSL. In the example below we index a product description in the `products` index, using the product’s SKU as the document identifier in the index. The `product` object will be mapped to JSON using the object mapper configured on the {{es}} client. -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -34,7 +34,7 @@ logger.info("Indexed with version " + response.version()); You can also assign objects created with the DSL to variables. Java API Client classes have a static `of()` method for this, that creates an object with the DSL syntax. -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-of +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-of ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -53,7 +53,7 @@ logger.info("Indexed with version " + response.version()); If you’re more used to the classic builder pattern, it is also available. Builder objects are used under the hood by the fluent DSL syntax. -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-builder +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-builder ```java Product product = new Product("bk-1", "City bike", 123.0); @@ -71,7 +71,7 @@ logger.info("Indexed with version " + response.version()); The examples above used the synchronous {{es}} client. All {{es}} APIs are also available in the asynchronous client, using the same request and response types. See also [Blocking and asynchronous clients](/reference/api-conventions/blocking-async.md) for additional details. -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-async +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-dsl-async ```java ElasticsearchAsyncClient esAsyncClient = new ElasticsearchAsyncClient(transport); @@ -96,7 +96,7 @@ When the data you want to index comes from external sources, having to create do You can index data from an arbitrary source using `withJson()`. Using this method will read the source and use it for the index request’s `document` property. See [Creating API objects from JSON data](/reference/api-conventions/loading-json.md) for additional details. -% :::include-code src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-json +% :::{include-code} src={{doc-tests-src}}/usage/IndexingTest.java tag=single-doc-json ```java Reader input = new StringReader( "{'@timestamp': '2022-04-08T13:55:32Z', 'level': 'warn', 'message': 'Some log message'}" diff --git a/docs/reference/usage/reading.md b/docs/reference/usage/reading.md index f49f39f65..1d1311899 100644 --- a/docs/reference/usage/reading.md +++ b/docs/reference/usage/reading.md @@ -22,7 +22,7 @@ The `get` request has two parameters: * the first parameter is the actual request, built below with the fluent DSL * the second parameter is the class we want the document’s JSON to be mapped to. -% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id +% :::{include-code} src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id ```java GetResponse response = esClient.get(g -> g .index("products") // <1> @@ -49,7 +49,7 @@ When your index contains semi-structured data or if you don’t have a domain ob Raw JSON data is just another class that you can use as the result type for the get request. In the example below we use Jackson’s `ObjectNode`. We could also have used any JSON representation that can be deserialized by the JSON mapper associated to the `ElasticsearchClient`. -% :::include-code src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id-json +% :::{include-code} src={{doc-tests-src}}/usage/ReadingTest.java tag=get-by-id-json ```java GetResponse response = esClient.get(g -> g .index("products") diff --git a/docs/reference/usage/searching.md b/docs/reference/usage/searching.md index 0d3363368..8724c6b51 100644 --- a/docs/reference/usage/searching.md +++ b/docs/reference/usage/searching.md @@ -23,7 +23,7 @@ The total value comes with a relation that indicates if the total is exact (`eq` Each returned document comes with its relevance score and additional information about its location in the index. -% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-simple +% :::{include-code} src={{doc-tests-src}}/usage/SearchingTest.java tag=search-simple ```java String searchText = "bike"; @@ -69,7 +69,7 @@ Similarly to [get](reading.md) operations, you can fetch documents matching your {{es}} allows individual queries to be combined to build more complex search requests. In the example below we will search for bikes with a maximum price of 200. -% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-nested +% :::{include-code} src={{doc-tests-src}}/usage/SearchingTest.java tag=search-nested ```java String searchText = "bike"; double maxPrice = 200.0; @@ -120,7 +120,7 @@ A search template is a stored search that you can run with different variables. Before running a template search, you first have to create the template. This is a stored script that returns the search request body, and is usually defined as a Mustache template. This stored script can be created outside the application, and also with the Java API Client: -% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-script +% :::{include-code} src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-script ```java // Create a script esClient.putScript(r -> r @@ -136,7 +136,7 @@ esClient.putScript(r -> r To use the search template, use the `searchTemplate` method to refer to the script and provide values for its parameters: -% :::include-code src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-query +% :::{include-code} src={{doc-tests-src}}/usage/SearchingTest.java tag=search-template-query ```java SearchTemplateResponse response = esClient.searchTemplate(r -> r .index("some-index") diff --git a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java index 6ff9f6026..c6bb56232 100644 --- a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java +++ b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java @@ -88,7 +88,7 @@ public static void fail(String message, String path, LineNumberReader reader, Th } public static String expandText(String input, Map subst, String path) throws IOException { - if (!input.contains("% :::include-code")) { + if (!input.contains("% :::{include-code}")) { // Nothing to do return null; } @@ -104,7 +104,7 @@ public static String expandText(String input, Map subst, String switch (state) { case NORMAL_TEXT -> { - if (line.startsWith("% :::include-code")) { + if (line.startsWith("% :::{include-code}")) { output.append(line).append("\n"); includeCodeLine = line; state = State.INCLUDE_CODE; @@ -117,7 +117,7 @@ public static String expandText(String input, Map subst, String case INCLUDE_CODE -> { if (!line.startsWith("```")) { - fail("The '% :::include-code' should be followed by a code block", path, reader, null); + fail("The '% :::{include-code}' should be followed by a code block", path, reader, null); } output.append(line).append("\n"); state = State.CODE_BLOCK; From e458016bb55cde7332bdf44fc0673a75c01eb060 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 08:36:04 +0200 Subject: [PATCH 07/14] Cleanup --- docs/docset.yml | 2 ++ .../clients/tools/docs/IncludeExpander.java | 32 ++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/docset.yml b/docs/docset.yml index 19cad0c5e..51e45c445 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -13,3 +13,5 @@ subs: es3: "Elasticsearch Serverless" es: "Elasticsearch" version: "9.0.0" + # Not used by docs-builder, but present in `% :::{include-bloc}` preprocessing directives (see IncludeExpander.java) + doc-tests-src: "../../java-client/src/test/java/co/elastic/clients/documentation" diff --git a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java index c6bb56232..aecd42b0e 100644 --- a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java +++ b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java @@ -30,17 +30,33 @@ import java.util.Map; import java.util.Objects; +/** + * Processes the {@code .md} files in a directory expanding code blocks following a custom directive + * (expressed as MyST comment): + *
+ *     % :::{include-code} src={{doc-tests-src}}/path/to/SomeCode.java tag=some-marker
+ *     ```java
+ *       this is replaced by the section of SomeCode.java delimited
+ *       with `tag::some-marker` and `end::some-marker`
+ *     ```
+ * 
+ * + * Note: the MyST format has the {@code {literalinclude}} directive, which is currently only partially supported + * by docs-builder (the whole file is included, ignoring {@code start-after} and {@code end-before} parameters). + *

+ * See MyST docs + */ public class IncludeExpander { public static void main(String[] args) throws IOException { - File dir = new File(args.length == 0 ? "docs/reference" : args[0]); if (!dir.isDirectory()) { throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory"); } + // FIXME: we should read this from `docset.yml` in the `dir` directory. processDirectory(dir, Map.of("doc-tests-src", "java-client/src/test/java/co/elastic/clients/documentation")); } @@ -79,8 +95,8 @@ public static void processFile(File file, Map subst) throws IOEx enum State { NORMAL_TEXT, - INCLUDE_CODE, - CODE_BLOCK, + INCLUDE_CODE_DIRECTIVE, + REPLACED_CODE_BLOCK, } public static void fail(String message, String path, LineNumberReader reader, Throwable e) { @@ -107,7 +123,7 @@ public static String expandText(String input, Map subst, String if (line.startsWith("% :::{include-code}")) { output.append(line).append("\n"); includeCodeLine = line; - state = State.INCLUDE_CODE; + state = State.INCLUDE_CODE_DIRECTIVE; } else { // Regular text line @@ -115,12 +131,12 @@ public static String expandText(String input, Map subst, String } } - case INCLUDE_CODE -> { + case INCLUDE_CODE_DIRECTIVE -> { if (!line.startsWith("```")) { fail("The '% :::{include-code}' should be followed by a code block", path, reader, null); } output.append(line).append("\n"); - state = State.CODE_BLOCK; + state = State.REPLACED_CODE_BLOCK; try { expandIncludeCodeDirective(includeCodeLine, subst, output); } catch (Exception e) { @@ -128,7 +144,7 @@ public static String expandText(String input, Map subst, String } } - case CODE_BLOCK -> { + case REPLACED_CODE_BLOCK -> { // Skip existing code until we reach the end if (line.startsWith("```")) { output.append(line).append("\n"); @@ -156,7 +172,7 @@ public static void expandIncludeCodeDirective(String command, Map Date: Wed, 9 Apr 2025 09:30:37 +0200 Subject: [PATCH 08/14] Checkstyle, rename 'client' to 'esClient' --- .../api_conventions/ApiConventionsTest.java | 22 +++++++++---------- .../api_conventions/LoadingJsonTest.java | 10 ++++----- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java index 8e3ad9dee..bc2d5e2d5 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/ApiConventionsTest.java @@ -35,8 +35,6 @@ import co.elastic.clients.util.ApiTypeHelper; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; import jakarta.json.JsonArray; import jakarta.json.JsonObject; import jakarta.json.JsonValue; @@ -62,9 +60,9 @@ public void blockingAndAsync() throws Exception { //tag::blocking-and-async // Synchronous blocking client - ElasticsearchClient client = new ElasticsearchClient(transport); + ElasticsearchClient esClient = new ElasticsearchClient(transport); - if (client.exists(b -> b.index("products").id("foo")).value()) { + if (esClient.exists(b -> b.index("products").id("foo")).value()) { logger.info("product exists"); } @@ -92,8 +90,8 @@ private ElasticsearchClient createClient() { public void builders() throws Exception { //tag::builders - ElasticsearchClient client = createClient(); - CreateIndexResponse createResponse = client.indices().create( + ElasticsearchClient esClient = createClient(); + CreateIndexResponse createResponse = esClient.indices().create( new CreateIndexRequest.Builder() .index("my-index") .aliases("foo", @@ -107,8 +105,8 @@ public void builders() throws Exception { public void builderLambdas() throws Exception { //tag::builder-lambdas - ElasticsearchClient client = createClient(); - CreateIndexResponse createResponse = client.indices() + ElasticsearchClient esClient = createClient(); + CreateIndexResponse createResponse = esClient.indices() .create(createIndexBuilder -> createIndexBuilder .index("my-index") .aliases("foo", aliasBuilder -> aliasBuilder @@ -121,8 +119,8 @@ public void builderLambdas() throws Exception { public void builderLambdasShort() throws Exception { //tag::builder-lambdas-short - ElasticsearchClient client = createClient(); - CreateIndexResponse createResponse = client.indices() + ElasticsearchClient esClient = createClient(); + CreateIndexResponse createResponse = esClient.indices() .create(c -> c .index("my-index") .aliases("foo", a -> a @@ -134,8 +132,8 @@ public void builderLambdasShort() throws Exception { public void builderIntervals() throws Exception { //tag::builder-intervals - ElasticsearchClient client = createClient(); - SearchResponse results = client + ElasticsearchClient esClient = createClient(); + SearchResponse results = esClient .search(b0 -> b0 .query(b1 -> b1 .intervals(b2 -> b2 diff --git a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java index 66d116ac3..af61cff32 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/api_conventions/LoadingJsonTest.java @@ -47,7 +47,7 @@ public class LoadingJsonTest extends ModelTestCase { private final DocTestsTransport transport = new DocTestsTransport(); - private final ElasticsearchClient client = new ElasticsearchClient(transport); + private final ElasticsearchClient esClient = new ElasticsearchClient(transport); private static final SearchResponse searchResponse = SearchResponse.of(b -> b .aggregations(new HashMap<>()) @@ -82,7 +82,7 @@ public void loadIndexDefinition() throws IOException { .withJson(input) // <2> ); - boolean created = client.indices().create(req).acknowledged(); + boolean created = esClient.indices().create(req).acknowledged(); //end::load-index } @@ -102,7 +102,7 @@ public void ingestDocument() throws IOException { .withJson(file) ); - client.index(req); + esClient.index(req); //end::ingest-data } @@ -136,7 +136,7 @@ public void query1() throws IOException { .size(0) ); - Map aggs = client + Map aggs = esClient .search(aggRequest, Void.class) // <3> .aggregations(); //end::query @@ -185,7 +185,7 @@ public void query2() throws IOException { .ignoreUnavailable(true) // <5> ); - Map aggs = client + Map aggs = esClient .search(aggRequest, Void.class) .aggregations(); //end::query-and-agg From db221c2464d5b1f1d9a7d028b78f426cbbe2353f Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 14:22:24 +0200 Subject: [PATCH 09/14] Add rest5_client docs --- .../reference/_snippets/legacy-rest-client.md | 3 + .../api-conventions/blocking-async.md | 4 +- .../api-conventions/building-objects.md | 16 +- .../reference/api-conventions/loading-json.md | 8 +- docs/reference/setup/installation.md | 2 +- docs/reference/toc.yml | 24 +++ .../config/basic_authentication.md | 3 + .../config/encrypted_communication.md | 3 + .../transport/rest-client/config/index.md | 3 + .../rest-client/config/node_selector.md | 3 + .../rest-client/config/number_of_threads.md | 3 + .../config/other_authentication_methods.md | 3 + .../transport/rest-client/config/others.md | 3 + .../transport/rest-client/config/timeouts.md | 3 + docs/reference/transport/rest-client/index.md | 3 + .../transport/rest-client/sniffer/index.md | 3 + .../transport/rest-client/sniffer/javadoc.md | 3 + .../rest-client/sniffer/maven_repository.md | 5 +- .../transport/rest-client/sniffer/usage.md | 3 + .../rest-client/usage/dependencies.md | 3 + .../transport/rest-client/usage/index.md | 3 + .../rest-client/usage/initialization.md | 3 + .../transport/rest-client/usage/javadoc.md | 3 + .../transport/rest-client/usage/logging.md | 3 + .../transport/rest-client/usage/maven.md | 3 + .../transport/rest-client/usage/requests.md | 3 + .../transport/rest-client/usage/responses.md | 3 + .../transport/rest-client/usage/shading.md | 3 + .../config/basic_authentication.md | 41 ++++ .../config/encrypted_communication.md | 84 ++++++++ .../transport/rest5-client/config/index.md | 14 ++ .../rest5-client/config/node_selector.md | 45 +++++ .../rest5-client/config/number_of_threads.md | 17 ++ .../config/other_authentication_methods.md | 45 +++++ .../transport/rest5-client/config/others.md | 10 + .../transport/rest5-client/config/timeouts.md | 33 +++ .../reference/transport/rest5-client/index.md | 13 ++ .../transport/rest5-client/sniffer/index.md | 120 +++++++++++ .../transport/rest5-client/usage/index.md | 13 ++ .../rest5-client/usage/initialization.md | 96 +++++++++ .../transport/rest5-client/usage/logging.md | 9 + .../transport/rest5-client/usage/requests.md | 191 ++++++++++++++++++ .../transport/rest5-client/usage/responses.md | 43 ++++ .../RestClientDocumentation.java | 173 +++++++++------- .../rest5_client}/SnifferDocumentation.java | 46 +++-- .../clients/tools/docs/IncludeExpander.java | 17 +- 46 files changed, 1024 insertions(+), 111 deletions(-) create mode 100644 docs/reference/_snippets/legacy-rest-client.md create mode 100644 docs/reference/transport/rest5-client/config/basic_authentication.md create mode 100644 docs/reference/transport/rest5-client/config/encrypted_communication.md create mode 100644 docs/reference/transport/rest5-client/config/index.md create mode 100644 docs/reference/transport/rest5-client/config/node_selector.md create mode 100644 docs/reference/transport/rest5-client/config/number_of_threads.md create mode 100644 docs/reference/transport/rest5-client/config/other_authentication_methods.md create mode 100644 docs/reference/transport/rest5-client/config/others.md create mode 100644 docs/reference/transport/rest5-client/config/timeouts.md create mode 100644 docs/reference/transport/rest5-client/index.md create mode 100644 docs/reference/transport/rest5-client/sniffer/index.md create mode 100644 docs/reference/transport/rest5-client/usage/index.md create mode 100644 docs/reference/transport/rest5-client/usage/initialization.md create mode 100644 docs/reference/transport/rest5-client/usage/logging.md create mode 100644 docs/reference/transport/rest5-client/usage/requests.md create mode 100644 docs/reference/transport/rest5-client/usage/responses.md rename java-client/src/test/java/co/elastic/clients/{transport/rest5_client/low_level/documentation => documentation/rest5_client}/RestClientDocumentation.java (80%) rename java-client/src/test/java/co/elastic/clients/{transport/rest5_client/low_level/sniffer/documentation => documentation/rest5_client}/SnifferDocumentation.java (85%) diff --git a/docs/reference/_snippets/legacy-rest-client.md b/docs/reference/_snippets/legacy-rest-client.md new file mode 100644 index 000000000..8f7c3d328 --- /dev/null +++ b/docs/reference/_snippets/legacy-rest-client.md @@ -0,0 +1,3 @@ +:::{note} +This is the legacy RestClient. Please migrate to [Rest5Client](/reference/transport/rest5-client/index.md), a drop-in replacement with an up-to-date http library. +::: diff --git a/docs/reference/api-conventions/blocking-async.md b/docs/reference/api-conventions/blocking-async.md index 3d1a04c26..41b6044eb 100644 --- a/docs/reference/api-conventions/blocking-async.md +++ b/docs/reference/api-conventions/blocking-async.md @@ -12,9 +12,9 @@ Both flavors can be used at the same time depending on your needs, sharing the s % :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=blocking-and-async ```java // Synchronous blocking client -ElasticsearchClient client = new ElasticsearchClient(transport); +ElasticsearchClient esClient = new ElasticsearchClient(transport); -if (client.exists(b -> b.index("products").id("foo")).value()) { +if (esClient.exists(b -> b.index("products").id("foo")).value()) { logger.info("product exists"); } diff --git a/docs/reference/api-conventions/building-objects.md b/docs/reference/api-conventions/building-objects.md index a9cf2e4bb..684c518c3 100644 --- a/docs/reference/api-conventions/building-objects.md +++ b/docs/reference/api-conventions/building-objects.md @@ -12,8 +12,8 @@ All data types in the Java API Client are immutable. Object creation uses the [b % :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builders ```java -ElasticsearchClient client = createClient(); -CreateIndexResponse createResponse = client.indices().create( +ElasticsearchClient esClient = createClient(); +CreateIndexResponse createResponse = esClient.indices().create( new CreateIndexRequest.Builder() .index("my-index") .aliases("foo", @@ -32,8 +32,8 @@ Although this works nicely, having to instantiate builder classes and call the ` % :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas ```java -ElasticsearchClient client = createClient(); -CreateIndexResponse createResponse = client.indices() +ElasticsearchClient esClient = createClient(); +CreateIndexResponse createResponse = esClient.indices() .create(createIndexBuilder -> createIndexBuilder .index("my-index") .aliases("foo", aliasBuilder -> aliasBuilder @@ -48,8 +48,8 @@ Note in the above example that builder variables are only used to start a chain % :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-lambdas-short ```java -ElasticsearchClient client = createClient(); -CreateIndexResponse createResponse = client.indices() +ElasticsearchClient esClient = createClient(); +CreateIndexResponse createResponse = esClient.indices() .create(c -> c .index("my-index") .aliases("foo", a -> a @@ -64,8 +64,8 @@ This example also highlights a useful naming convention for builder parameters i % :::{include-code} src={{doc-tests-src}}/api_conventions/ApiConventionsTest.java tag=builder-intervals ```java -ElasticsearchClient client = createClient(); -SearchResponse results = client +ElasticsearchClient esClient = createClient(); +SearchResponse results = esClient .search(b0 -> b0 .query(b1 -> b1 .intervals(b2 -> b2 diff --git a/docs/reference/api-conventions/loading-json.md b/docs/reference/api-conventions/loading-json.md index 08e7a677d..7cda36d71 100644 --- a/docs/reference/api-conventions/loading-json.md +++ b/docs/reference/api-conventions/loading-json.md @@ -41,7 +41,7 @@ CreateIndexRequest req = CreateIndexRequest.of(b -> b .withJson(input) // <2> ); -boolean created = client.indices().create(req).acknowledged(); +boolean created = esClient.indices().create(req).acknowledged(); ``` 1. open an input stream for the JSON resource file. @@ -64,7 +64,7 @@ req = IndexRequest.of(b -> b .withJson(file) ); -client.index(req); +esClient.index(req); ``` 1. when calling `withJson()` on data structures that have generic type parameters, these generic types will be considered to be `JsonData`. @@ -102,7 +102,7 @@ SearchRequest aggRequest = SearchRequest.of(b -> b .size(0) ); -Map aggs = client +Map aggs = esClient .search(aggRequest, Void.class) // <3> .aggregations(); ``` @@ -157,7 +157,7 @@ SearchRequest aggRequest = SearchRequest.of(b -> b .ignoreUnavailable(true) // <5> ); -Map aggs = client +Map aggs = esClient .search(aggRequest, Void.class) .aggregations(); ``` diff --git a/docs/reference/setup/installation.md b/docs/reference/setup/installation.md index 366126a4b..c26d29f35 100644 --- a/docs/reference/setup/installation.md +++ b/docs/reference/setup/installation.md @@ -26,7 +26,7 @@ dependencies { In the `pom.xml` of your project, add the following repository definition and dependencies: -```xml +```xml subs=true diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml index 094953c5c..5885bac01 100644 --- a/docs/reference/toc.yml +++ b/docs/reference/toc.yml @@ -39,6 +39,30 @@ toc: children: - file: index.md + - folder: rest5-client + children: + - file: index.md + - folder: usage + children: + - file: index.md + - file: initialization.md + - file: requests.md + - file: responses.md + - file: logging.md + - folder: config + children: + - file: index.md + - file: timeouts.md + - file: number_of_threads.md + - file: basic_authentication.md + - file: other_authentication_methods.md + - file: encrypted_communication.md + - file: others.md + - file: node_selector.md + - folder: sniffer + children: + - file: index.md + - folder: rest-client children: - file: index.md diff --git a/docs/reference/transport/rest-client/config/basic_authentication.md b/docs/reference/transport/rest-client/config/basic_authentication.md index 5e54beed3..9fb1a35ac 100644 --- a/docs/reference/transport/rest-client/config/basic_authentication.md +++ b/docs/reference/transport/rest-client/config/basic_authentication.md @@ -5,6 +5,9 @@ mapped_pages: # Basic authentication [_basic_authentication] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Configuring basic authentication can be done by providing an `HttpClientConfigCallback` while building the `RestClient` through its builder. The interface has one method that receives an instance of [`org.apache.http.impl.nio.client.HttpAsyncClientBuilder`](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/current/httpasyncclient/apidocs/org/apache/http/impl/nio/client/HttpAsyncClientBuilder.html) as an argument and has the same return type. The http client builder can be modified and then returned. In the following example we set a default credentials provider that requires basic authentication. ```java diff --git a/docs/reference/transport/rest-client/config/encrypted_communication.md b/docs/reference/transport/rest-client/config/encrypted_communication.md index 8edab1187..ac18c4b69 100644 --- a/docs/reference/transport/rest-client/config/encrypted_communication.md +++ b/docs/reference/transport/rest-client/config/encrypted_communication.md @@ -5,6 +5,9 @@ mapped_pages: # Encrypted communication [_encrypted_communication] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Encrypted communication using TLS can also be configured through the `HttpClientConfigCallback`. The [`org.apache.http.impl.nio.client.HttpAsyncClientBuilder`](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/current/httpasyncclient/apidocs/org/apache/http/impl/nio/client/HttpAsyncClientBuilder.html) received as an argument exposes multiple methods to configure encrypted communication: `setSSLContext`, `setSSLSessionStrategy` and `setConnectionManager`, in order of precedence from the least important. When accessing an Elasticsearch cluster that is setup for TLS on the HTTP layer, the client needs to trust the certificate that Elasticsearch is using. The following is an example of setting up the client to trust the CA that has signed the certificate that Elasticsearch is using, when that CA certificate is available in a PKCS#12 keystore: diff --git a/docs/reference/transport/rest-client/config/index.md b/docs/reference/transport/rest-client/config/index.md index afadab117..d2ddf09f1 100644 --- a/docs/reference/transport/rest-client/config/index.md +++ b/docs/reference/transport/rest-client/config/index.md @@ -5,6 +5,9 @@ mapped_pages: # Common configuration [java-rest-low-config] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + As explained in [Initialization](../usage/initialization.md), the `RestClientBuilder` supports providing both a `RequestConfigCallback` and an `HttpClientConfigCallback` which allow for any customization that the Apache Async Http Client exposes. Those callbacks make it possible to modify some specific behaviour of the client without overriding every other default configuration that the `RestClient` is initialized with. This section describes some common scenarios that require additional configuration for the low-level Java REST Client. diff --git a/docs/reference/transport/rest-client/config/node_selector.md b/docs/reference/transport/rest-client/config/node_selector.md index 1cd0f3408..702dc3c55 100644 --- a/docs/reference/transport/rest-client/config/node_selector.md +++ b/docs/reference/transport/rest-client/config/node_selector.md @@ -5,6 +5,9 @@ mapped_pages: # Node selector [_node_selector] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The client sends each request to one of the configured nodes in round-robin fashion. Nodes can optionally be filtered through a node selector that needs to be provided when initializing the client. This is useful when sniffing is enabled, in case no dedicated master nodes should be hit by HTTP requests. For each request the client will run the eventually configured node selector to filter the node candidates, then select the next one in the list out of the remaining ones. ```java diff --git a/docs/reference/transport/rest-client/config/number_of_threads.md b/docs/reference/transport/rest-client/config/number_of_threads.md index 318c2d407..8589f2837 100644 --- a/docs/reference/transport/rest-client/config/number_of_threads.md +++ b/docs/reference/transport/rest-client/config/number_of_threads.md @@ -5,6 +5,9 @@ mapped_pages: # Number of threads [_number_of_threads] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The Apache Http Async Client starts by default one dispatcher thread, and a number of worker threads used by the connection manager, as many as the number of locally detected processors (depending on what `Runtime.getRuntime().availableProcessors()` returns). The number of threads can be modified as follows: ```java diff --git a/docs/reference/transport/rest-client/config/other_authentication_methods.md b/docs/reference/transport/rest-client/config/other_authentication_methods.md index ef3e88fb0..a03984c8e 100644 --- a/docs/reference/transport/rest-client/config/other_authentication_methods.md +++ b/docs/reference/transport/rest-client/config/other_authentication_methods.md @@ -5,6 +5,9 @@ mapped_pages: # Other authentication methods [_other_authentication_methods] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + ## Elasticsearch Token Service tokens [_elasticsearch_token_service_tokens] If you want the client to authenticate with an Elasticsearch access token, set the relevant HTTP request header. If the client makes requests on behalf of a single user only, you can set the necessary `Authorization` header as a default header as shown in the following example: diff --git a/docs/reference/transport/rest-client/config/others.md b/docs/reference/transport/rest-client/config/others.md index 6232fb867..61eafa1df 100644 --- a/docs/reference/transport/rest-client/config/others.md +++ b/docs/reference/transport/rest-client/config/others.md @@ -5,6 +5,9 @@ mapped_pages: # Others [_others] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + For any other required configuration needed, the Apache HttpAsyncClient docs should be consulted: [https://hc.apache.org/httpcomponents-asyncclient-4.1.x/](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/) . ::::{note} diff --git a/docs/reference/transport/rest-client/config/timeouts.md b/docs/reference/transport/rest-client/config/timeouts.md index b361207a9..b62e8901e 100644 --- a/docs/reference/transport/rest-client/config/timeouts.md +++ b/docs/reference/transport/rest-client/config/timeouts.md @@ -5,6 +5,9 @@ mapped_pages: # Timeouts [_timeouts] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Configuring requests timeouts can be done by providing an instance of `RequestConfigCallback` while building the `RestClient` through its builder. The interface has one method that receives an instance of [`org.apache.http.client.config.RequestConfig.Builder`](https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/org/apache/http/client/config/RequestConfig.Builder.html) as an argument and has the same return type. The request config builder can be modified and then returned. In the following example we increase the connect timeout (defaults to 1 second) and the socket timeout (defaults to 30 seconds). ```java diff --git a/docs/reference/transport/rest-client/index.md b/docs/reference/transport/rest-client/index.md index f2144c0db..dff81e2c1 100644 --- a/docs/reference/transport/rest-client/index.md +++ b/docs/reference/transport/rest-client/index.md @@ -5,6 +5,9 @@ mapped_pages: # Legacy REST Client [java-rest-low] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The low-level client’s features include: * minimal dependencies diff --git a/docs/reference/transport/rest-client/sniffer/index.md b/docs/reference/transport/rest-client/sniffer/index.md index 883ab087a..61ee38a3d 100644 --- a/docs/reference/transport/rest-client/sniffer/index.md +++ b/docs/reference/transport/rest-client/sniffer/index.md @@ -5,6 +5,9 @@ mapped_pages: # Sniffer [sniffer] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Minimal library that allows to automatically discover nodes from a running Elasticsearch cluster and set them to an existing `RestClient` instance. It retrieves by default the nodes that belong to the cluster using the Nodes Info api and uses jackson to parse the obtained json response. diff --git a/docs/reference/transport/rest-client/sniffer/javadoc.md b/docs/reference/transport/rest-client/sniffer/javadoc.md index 6088ce0a0..3a1f9b5e9 100644 --- a/docs/reference/transport/rest-client/sniffer/javadoc.md +++ b/docs/reference/transport/rest-client/sniffer/javadoc.md @@ -5,5 +5,8 @@ mapped_pages: # Javadoc [java-rest-sniffer-javadoc] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The javadoc for the REST client sniffer can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/{{version}}/index.html](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/{{version}}/index.md). diff --git a/docs/reference/transport/rest-client/sniffer/maven_repository.md b/docs/reference/transport/rest-client/sniffer/maven_repository.md index 3b327b1ea..d86caaddf 100644 --- a/docs/reference/transport/rest-client/sniffer/maven_repository.md +++ b/docs/reference/transport/rest-client/sniffer/maven_repository.md @@ -5,6 +5,9 @@ mapped_pages: # Maven Repository [_maven_repository] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The REST client sniffer is subject to the same release cycle as Elasticsearch. Replace the version with the desired sniffer version, first released with `5.0.0-alpha4`. There is no relation between the sniffer version and the Elasticsearch version that the client can communicate with. Sniffer supports fetching the nodes list from Elasticsearch 2.x and onwards. If you are looking for a SNAPSHOT version, the Elastic Maven Snapshot repository is available at [https://snapshots.elastic.co/maven/](https://snapshots.elastic.co/maven/). @@ -28,7 +31,7 @@ Here is how you can configure the dependency using gradle as a dependency manage ```groovy dependencies { - compile 'org.elasticsearch.client:elasticsearch-rest-client-sniffer:9.0.0-beta1' + compile 'org.elasticsearch.client:elasticsearch-rest-client-sniffer:{{version}}' } ``` diff --git a/docs/reference/transport/rest-client/sniffer/usage.md b/docs/reference/transport/rest-client/sniffer/usage.md index 540f73a02..73dbd58f3 100644 --- a/docs/reference/transport/rest-client/sniffer/usage.md +++ b/docs/reference/transport/rest-client/sniffer/usage.md @@ -5,6 +5,9 @@ mapped_pages: # Usage [_usage] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Once a `RestClient` instance has been created as shown in [Initialization](../usage/initialization.md), a `Sniffer` can be associated to it. The `Sniffer` will make use of the provided `RestClient` to periodically (every 5 minutes by default) fetch the list of current nodes from the cluster and update them by calling `RestClient#setNodes`. ```java diff --git a/docs/reference/transport/rest-client/usage/dependencies.md b/docs/reference/transport/rest-client/usage/dependencies.md index 3bc944a11..70a9cd85e 100644 --- a/docs/reference/transport/rest-client/usage/dependencies.md +++ b/docs/reference/transport/rest-client/usage/dependencies.md @@ -5,6 +5,9 @@ mapped_pages: # Dependencies [java-rest-low-usage-dependencies] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The low-level Java REST client internally uses the [Apache Http Async Client](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/) to send http requests. It depends on the following artifacts, namely the async http client and its own transitive dependencies: * org.apache.httpcomponents:httpasyncclient diff --git a/docs/reference/transport/rest-client/usage/index.md b/docs/reference/transport/rest-client/usage/index.md index 8c87b0d4e..dd5897ca4 100644 --- a/docs/reference/transport/rest-client/usage/index.md +++ b/docs/reference/transport/rest-client/usage/index.md @@ -5,6 +5,9 @@ mapped_pages: # Getting started [java-rest-low-usage] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + This section describes how to get started with the low-level REST client from getting the artifact to using it in an application. diff --git a/docs/reference/transport/rest-client/usage/initialization.md b/docs/reference/transport/rest-client/usage/initialization.md index b4f946f66..dae72fdd9 100644 --- a/docs/reference/transport/rest-client/usage/initialization.md +++ b/docs/reference/transport/rest-client/usage/initialization.md @@ -5,6 +5,9 @@ mapped_pages: # Initialization [java-rest-low-usage-initialization] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + A `RestClient` instance can be built through the corresponding `RestClientBuilder` class, created via `RestClient#builder(HttpHost...)` static method. The only required argument is one or more hosts that the client will communicate with, provided as instances of [HttpHost](https://hc.apache.org/httpcomponents-core-4.4.x/current/httpcore/apidocs/org/apache/http/HttpHost.html) as follows: ```java diff --git a/docs/reference/transport/rest-client/usage/javadoc.md b/docs/reference/transport/rest-client/usage/javadoc.md index cb9299544..a427a05b4 100644 --- a/docs/reference/transport/rest-client/usage/javadoc.md +++ b/docs/reference/transport/rest-client/usage/javadoc.md @@ -5,5 +5,8 @@ mapped_pages: # Javadoc [java-rest-low-javadoc] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The javadoc for the low level REST client can be found at [https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/{{version}}/](https://snapshots.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/{{version}}/). diff --git a/docs/reference/transport/rest-client/usage/logging.md b/docs/reference/transport/rest-client/usage/logging.md index 5f65fa0e8..bd6dcd80d 100644 --- a/docs/reference/transport/rest-client/usage/logging.md +++ b/docs/reference/transport/rest-client/usage/logging.md @@ -5,6 +5,9 @@ mapped_pages: # Logging [java-rest-low-usage-logging] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The Java REST client uses the same logging library that the Apache Async Http Client uses: [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/), which comes with support for a number of popular logging implementations. The java packages to enable logging for are `org.elasticsearch.client` for the client itself and `org.elasticsearch.client.sniffer` for the sniffer. The request tracer logging can also be enabled to log every request and corresponding response in curl format. That comes handy when debugging, for instance in case a request needs to be manually executed to check whether it still yields the same response as it did. Enable trace logging for the `tracer` package to have such log lines printed out. Do note that this type of logging is expensive and should not be enabled at all times in production environments, but rather temporarily used only when needed. diff --git a/docs/reference/transport/rest-client/usage/maven.md b/docs/reference/transport/rest-client/usage/maven.md index af4248fac..df88bea06 100644 --- a/docs/reference/transport/rest-client/usage/maven.md +++ b/docs/reference/transport/rest-client/usage/maven.md @@ -5,6 +5,9 @@ mapped_pages: # Maven Repository [java-rest-low-usage-maven] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The low-level Java REST client is hosted on [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.elasticsearch.client%22). The minimum Java version required is `1.8`. The low-level REST client is subject to the same release cycle as Elasticsearch. Replace the version with the desired client version, first released with `5.0.0-alpha4`. There is no relation between the client version and the Elasticsearch version that the client can communicate with. The low-level REST client is compatible with all Elasticsearch versions. diff --git a/docs/reference/transport/rest-client/usage/requests.md b/docs/reference/transport/rest-client/usage/requests.md index 0bf474b6f..5632259e5 100644 --- a/docs/reference/transport/rest-client/usage/requests.md +++ b/docs/reference/transport/rest-client/usage/requests.md @@ -5,6 +5,9 @@ mapped_pages: # Performing requests [java-rest-low-usage-requests] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + Once the `RestClient` has been created, requests can be sent by calling either `performRequest` or `performRequestAsync`. `performRequest` is synchronous and will block the calling thread and return the `Response` when the request is successful or throw an exception if it fails. `performRequestAsync` is asynchronous and accepts a `ResponseListener` argument that it calls with a `Response` when the request is successful or with an `Exception` if it fails. This is synchronous: diff --git a/docs/reference/transport/rest-client/usage/responses.md b/docs/reference/transport/rest-client/usage/responses.md index dcf22186f..5ac4ee9dd 100644 --- a/docs/reference/transport/rest-client/usage/responses.md +++ b/docs/reference/transport/rest-client/usage/responses.md @@ -5,6 +5,9 @@ mapped_pages: # Reading responses [java-rest-low-usage-responses] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + The `Response` object, either returned by the synchronous `performRequest` methods or received as an argument in `ResponseListener#onSuccess(Response)`, wraps the response object returned by the http client and exposes some additional information. ```java diff --git a/docs/reference/transport/rest-client/usage/shading.md b/docs/reference/transport/rest-client/usage/shading.md index 20507a697..78f594056 100644 --- a/docs/reference/transport/rest-client/usage/shading.md +++ b/docs/reference/transport/rest-client/usage/shading.md @@ -5,6 +5,9 @@ mapped_pages: # Shading [java-rest-low-usage-shading] +:::{include} /reference/_snippets/legacy-rest-client.md +::: + In order to avoid version conflicts, the dependencies can be shaded and packaged within the client in a single JAR file (sometimes called an "uber JAR" or "fat JAR"). Shading a dependency consists of taking its content (resources files and Java class files) and renaming some of its packages before putting them in the same JAR file as the low-level Java REST client. Shading a JAR can be accomplished by 3rd-party plugins for Gradle and Maven. Be advised that shading a JAR also has implications. Shading the Commons Logging layer, for instance, means that 3rd-party logging backends need to be shaded as well. diff --git a/docs/reference/transport/rest5-client/config/basic_authentication.md b/docs/reference/transport/rest5-client/config/basic_authentication.md new file mode 100644 index 000000000..077fbee17 --- /dev/null +++ b/docs/reference/transport/rest5-client/config/basic_authentication.md @@ -0,0 +1,41 @@ + +# Basic authentication + +Configuring basic authentication can be done by providing an `HttpClientConfigCallback` while building the `RestClient` through its builder. The interface has one method that receives an instance of [`org.apache.http.impl.nio.client.HttpAsyncClientBuilder`](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/current/httpasyncclient/apidocs/org/apache/http/impl/nio/client/HttpAsyncClientBuilder.html) as an argument and has the same return type. The http client builder can be modified and then returned. In the following example we set a default credentials provider that requires basic authentication. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-basic-auth +```java +var creds = Base64.getEncoder() + .encodeToString("user:test-user-password".getBytes()); + +Rest5ClientBuilder restClient = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) + .setDefaultHeaders(new Header[]{ + new BasicHeader("Authorization", "Basic " + creds) + }); + +``` + +Preemptive Authentication can be disabled, which means that every request will be sent without authorization headers to see if it is accepted and, upon receiving an HTTP 401 response, it will resend the exact same request with the basic authentication header. If you wish to do this, then you can do so by disabling it via the `HttpAsyncClientBuilder`: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-disable-preemptive-auth +```java +HttpHost host = new HttpHost("http", "localhost", 9200); + +var creds = Base64.getEncoder().encodeToString("user:test-user-password".getBytes()); + +CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .disableAuthCaching() // <1> + .build(); + +Rest5ClientBuilder restClient = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) + .setHttpClient(httpclient) + .setDefaultHeaders(new Header[]{ + new BasicHeader("Authorization", "Basic " + creds) + }); +``` + +1. Disable preemptive authentication + + diff --git a/docs/reference/transport/rest5-client/config/encrypted_communication.md b/docs/reference/transport/rest5-client/config/encrypted_communication.md new file mode 100644 index 000000000..d978106dc --- /dev/null +++ b/docs/reference/transport/rest5-client/config/encrypted_communication.md @@ -0,0 +1,84 @@ + +# Encrypted communication + +Encrypted communication using TLS can also be configured through the `HttpClientConfigCallback`. The [`org.apache.http.impl.nio.client.HttpAsyncClientBuilder`](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/current/httpasyncclient/apidocs/org/apache/http/impl/nio/client/HttpAsyncClientBuilder.html) received as an argument exposes multiple methods to configure encrypted communication: `setSSLContext`, `setSSLSessionStrategy` and `setConnectionManager`, in order of precedence from the least important. + +When accessing an Elasticsearch cluster that is setup for TLS on the HTTP layer, the client needs to trust the certificate that Elasticsearch is using. The following is an example of setting up the client to trust the CA that has signed the certificate that Elasticsearch is using, when that CA certificate is available in a PKCS#12 keystore: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-encrypted-communication +```java +Path trustStorePath = Paths.get("/path/to/truststore.p12"); +KeyStore truststore = KeyStore.getInstance("pkcs12"); +try (InputStream is = Files.newInputStream(trustStorePath)) { + truststore.load(is, keyStorePass.toCharArray()); +} + +SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(truststore, null) + .build(); + +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) + .setSSLContext(sslContext); +``` + +The following is an example of setting up the client to trust the CA that has signed the certificate that Elasticsearch is using, when that CA certificate is available as a PEM encoded file. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-trust-ca-pem +```java +Path caCertificatePath = Paths.get("/path/to/ca.crt"); +CertificateFactory factory = CertificateFactory.getInstance("X.509"); +Certificate trustedCa; +try (InputStream is = Files.newInputStream(caCertificatePath)) { + trustedCa = factory.generateCertificate(is); +} + +KeyStore trustStore = KeyStore.getInstance("pkcs12"); +trustStore.load(null, null); +trustStore.setCertificateEntry("ca", trustedCa); + +SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(trustStore, null) + .build(); + +Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) + .setSSLContext(sslContext); +``` + +When Elasticsearch is configured to require client TLS authentication, for example when a PKI realm is configured, the client needs to provide a client certificate during the TLS handshake in order to authenticate. The following is an example of setting up the client for TLS authentication with a certificate and a private key that are stored in a PKCS#12 keystore. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-mutual-tls-authentication +```java +Path trustStorePath = Paths.get("/path/to/your/truststore.p12"); +Path keyStorePath = Paths.get("/path/to/your/keystore.p12"); +KeyStore trustStore = KeyStore.getInstance("pkcs12"); +KeyStore keyStore = KeyStore.getInstance("pkcs12"); + +try (InputStream is = Files.newInputStream(trustStorePath)) { + trustStore.load(is, trustStorePass.toCharArray()); +} + +try (InputStream is = Files.newInputStream(keyStorePath)) { + keyStore.load(is, keyStorePass.toCharArray()); +} + +SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(trustStore, null) + .loadKeyMaterial(keyStore, keyStorePass.toCharArray()) + .build(); + +Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) + .setSSLContext(sslContext); +``` + +If the client certificate and key are not available in a keystore but rather as PEM encoded files, you cannot use them directly to build an SSLContext. You must rely on external libraries to parse the PEM key into a PrivateKey instance. Alternatively, you can use external tools to build a keystore from your PEM files, as shown in the following example: + +```bash +openssl pkcs12 -export -in client.crt -inkey private_key.pem \ + -name "client" -out client.p12 +``` + +If no explicit configuration is provided, the [system default configuration](https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.md#CustomizingStores) will be used. + diff --git a/docs/reference/transport/rest5-client/config/index.md b/docs/reference/transport/rest5-client/config/index.md new file mode 100644 index 000000000..0e9b5f77d --- /dev/null +++ b/docs/reference/transport/rest5-client/config/index.md @@ -0,0 +1,14 @@ + +# Common configuration + +As explained in [Initialization](../usage/initialization.md), the `RestClientBuilder` supports providing both a `RequestConfigCallback` and an `HttpClientConfigCallback` which allow for any customization that the Apache Async Http Client exposes. Those callbacks make it possible to modify some specific behaviour of the client without overriding every other default configuration that the `Rest5Client` is initialized with. This section describes some common scenarios that require additional configuration for the low-level Java REST Client. + + + + + + + + diff --git a/docs/reference/transport/rest5-client/config/node_selector.md b/docs/reference/transport/rest5-client/config/node_selector.md new file mode 100644 index 000000000..e06f9f1b1 --- /dev/null +++ b/docs/reference/transport/rest5-client/config/node_selector.md @@ -0,0 +1,45 @@ + +# Node selector + +The client sends each request to one of the configured nodes in round-robin fashion. Nodes can optionally be filtered through a node selector that needs to be provided when initializing the client. This is useful when sniffing is enabled, in case no dedicated master nodes should be hit by HTTP requests. For each request the client will run the eventually configured node selector to filter the node candidates, then select the next one in the list out of the remaining ones. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-init-allocation-aware-selector +```java +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + +builder.setNodeSelector(nodes -> { // <1> + /* + * Prefer any node that belongs to rack_one. If none is around + * we will go to another rack till it's time to try and revive + * some of the nodes that belong to rack_one. + */ + boolean foundOne = false; + for (Node node : nodes) { + String rackId = node.getAttributes().get("rack_id").get(0); + if ("rack_one".equals(rackId)) { + foundOne = true; + break; + } + } + if (foundOne) { + Iterator nodesIt = nodes.iterator(); + while (nodesIt.hasNext()) { + Node node = nodesIt.next(); + String rackId = node.getAttributes().get("rack_id").get(0); + if (!"rack_one".equals(rackId)) { + nodesIt.remove(); + } + } + } +}); +``` + +1. Set an allocation aware node selector that allows to pick a node in the local rack if any available, otherwise go to any other node in any rack. It acts as a preference rather than a strict requirement, given that it goes to another rack if none of the local nodes are available, rather than returning no nodes in such case which would make the client forcibly revive a local node whenever none of the nodes from the preferred rack is available. + + +::::{warning} +Node selectors that do not consistently select the same set of nodes will make round-robin behaviour unpredictable and possibly unfair. The preference example above is fine as it reasons about availability of nodes which already affects the predictability of round-robin. Node selection should not depend on other external factors or round-robin will not work properly. +:::: + + diff --git a/docs/reference/transport/rest5-client/config/number_of_threads.md b/docs/reference/transport/rest5-client/config/number_of_threads.md new file mode 100644 index 000000000..a5be90de1 --- /dev/null +++ b/docs/reference/transport/rest5-client/config/number_of_threads.md @@ -0,0 +1,17 @@ + +# Number of threads + +The Apache Http Async Client starts by default one dispatcher thread, and a number of worker threads used by the connection manager, as many as the number of locally detected processors (depending on what `Runtime.getRuntime().availableProcessors()` returns). The number of threads can be modified as follows: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-threads +```java +CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setIOReactorConfig( + IOReactorConfig.custom().setIoThreadCount(1).build() + ) + .build(); + +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("localhost", 9200)) + .setHttpClient(httpclient); +``` diff --git a/docs/reference/transport/rest5-client/config/other_authentication_methods.md b/docs/reference/transport/rest5-client/config/other_authentication_methods.md new file mode 100644 index 000000000..584cc1b6a --- /dev/null +++ b/docs/reference/transport/rest5-client/config/other_authentication_methods.md @@ -0,0 +1,45 @@ + +# Other authentication methods + +## Elasticsearch Token Service tokens + +If you want the client to authenticate with an Elasticsearch access token, set the relevant HTTP request header. If the client makes requests on behalf of a single user only, you can set the necessary `Authorization` header as a default header as shown in the following example: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-auth-bearer-token +```java +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)); + +Header[] defaultHeaders = new Header[]{ new BasicHeader( + "Authorization", + "Bearer u6iuAxZ0RG1Kcm5jVFI4eU4tZU9aVFEwT2F3" +)}; + +builder.setDefaultHeaders(defaultHeaders); +``` + + +## Elasticsearch API keys + +If you want the client to authenticate with an Elasticsearch API key, set the relevant HTTP request header. If the client makes requests on behalf of a single user only, you can set the necessary `Authorization` header as a default header as shown in the following example: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-auth-api-key +```java +String apiKeyId = "uqlEyn8B_gQ_jlvwDIvM"; +String apiKeySecret = "HxHWk2m4RN-V_qg9cDpuX"; +String apiKeyAuth = Base64.getEncoder().encodeToString( + (apiKeyId + ":" + apiKeySecret).getBytes(StandardCharsets.UTF_8) +); + +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)); + +Header[] defaultHeaders = new Header[]{ new BasicHeader( + "Authorization", + "ApiKey " + apiKeyAuth +)}; + +builder.setDefaultHeaders(defaultHeaders); +``` + + diff --git a/docs/reference/transport/rest5-client/config/others.md b/docs/reference/transport/rest5-client/config/others.md new file mode 100644 index 000000000..e99b6870f --- /dev/null +++ b/docs/reference/transport/rest5-client/config/others.md @@ -0,0 +1,10 @@ + +# Others + +For any other required configuration needed, the Apache HttpAsyncClient docs should be consulted: [https://hc.apache.org/httpcomponents-asyncclient-4.1.x/](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/) . + +::::{note} +If your application runs under the security manager you might be subject to the JVM default policies of caching positive hostname resolutions indefinitely and negative hostname resolutions for ten seconds. If the resolved addresses of the hosts to which you are connecting the client to vary with time then you might want to modify the default JVM behavior. These can be modified by adding [`networkaddress.cache.ttl=`](https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.md) and [`networkaddress.cache.negative.ttl=`](https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.md) to your [Java security policy](https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.md). +:::: + + diff --git a/docs/reference/transport/rest5-client/config/timeouts.md b/docs/reference/transport/rest5-client/config/timeouts.md new file mode 100644 index 000000000..5b43b9b19 --- /dev/null +++ b/docs/reference/transport/rest5-client/config/timeouts.md @@ -0,0 +1,33 @@ + +# Timeouts + +Configuring requests timeouts can be done by providing an instance of `RequestConfigCallback` while building the `RestClient` through its builder. The interface has one method that receives an instance of [`org.apache.http.client.config.RequestConfig.Builder`](https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/org/apache/http/client/config/RequestConfig.Builder.html) as an argument and has the same return type. The request config builder can be modified and then returned. In the following example we increase the connect timeout (defaults to 1 second) and the socket timeout (defaults to 30 seconds). + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-timeouts +```java +RequestConfig.Builder requestConfigBuilder = RequestConfig.custom() + .setConnectTimeout(Timeout.of(5000, TimeUnit.MILLISECONDS)); + +CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setDefaultRequestConfig(requestConfigBuilder.build()) + .build(); + +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("localhost", 9200)) + .setHttpClient(httpclient); +``` + +Timeouts also can be set per request with RequestOptions, which overrides RestClient customizeRequestConfig. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-config-request-options-timeouts +```java +RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(5000)) + .setConnectionRequestTimeout(Timeout.ofMilliseconds(60000)) + .build(); + +RequestOptions options = RequestOptions.DEFAULT.toBuilder() + .setRequestConfig(requestConfig) + .build(); +``` + diff --git a/docs/reference/transport/rest5-client/index.md b/docs/reference/transport/rest5-client/index.md new file mode 100644 index 000000000..d6afed595 --- /dev/null +++ b/docs/reference/transport/rest5-client/index.md @@ -0,0 +1,13 @@ + +# REST 5 Client + +The low-level client’s features include: + +* minimal dependencies +* load balancing across all available nodes +* failover in case of node failures and upon specific response codes +* failed connection penalization (whether a failed node is retried depends on how many consecutive times it failed; the more failed attempts the longer the client will wait before trying that same node again) +* persistent connections +* trace logging of requests and responses +* optional automatic [discovery of cluster nodes](sniffer/index.md) + diff --git a/docs/reference/transport/rest5-client/sniffer/index.md b/docs/reference/transport/rest5-client/sniffer/index.md new file mode 100644 index 000000000..74b43e4d4 --- /dev/null +++ b/docs/reference/transport/rest5-client/sniffer/index.md @@ -0,0 +1,120 @@ + +# Sniffer + +Minimal library that allows to automatically discover nodes from a running Elasticsearch cluster and set them to an existing `RestClient` instance. It retrieves by default the nodes that belong to the cluster using the Nodes Info api and uses jackson to parse the obtained json response. + +# Usage + +Once a `RestClient` instance has been created as shown in [Initialization](../usage/initialization.md), a `Sniffer` can be associated to it. The `Sniffer` will make use of the provided `RestClient` to periodically (every 5 minutes by default) fetch the list of current nodes from the cluster and update them by calling `RestClient#setNodes`. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniffer-init +```java +Rest5Client restClient = Rest5Client + .builder(HttpHost.create("http://localhost:9200")) + .build(); + +Sniffer sniffer = Sniffer.builder(restClient).build(); +``` + +It is important to close the `Sniffer` so that its background thread gets properly shutdown and all of its resources are released. The `Sniffer` object should have the same lifecycle as the `RestClient` and get closed right before the client: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniffer-close +```java +sniffer.close(); +restClient.close(); +``` + +The `Sniffer` updates the nodes by default every 5 minutes. This interval can be customized by providing it (in milliseconds) as follows: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniffer-interval +```java +Rest5Client restClient = Rest5Client + .builder(new HttpHost("localhost", 9200)) + .build(); + +Sniffer sniffer = Sniffer.builder(restClient) + .setSniffIntervalMillis(60000).build(); +``` + +It is also possible to enable sniffing on failure, meaning that after each failure the nodes list gets updated straightaway rather than at the following ordinary sniffing round. In this case a `SniffOnFailureListener` needs to be created at first and provided at `RestClient` creation. Also once the `Sniffer` is later created, it needs to be associated with that same `SniffOnFailureListener` instance, which will be notified at each failure and use the `Sniffer` to perform the additional sniffing round as described. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniff-on-failure +```java +SniffOnFailureListener sniffOnFailureListener = + new SniffOnFailureListener(); + +Rest5Client restClient = Rest5Client + .builder(new HttpHost("localhost", 9200)) + .setFailureListener(sniffOnFailureListener) // <1> + .build(); + +Sniffer sniffer = Sniffer.builder(restClient) + .setSniffAfterFailureDelayMillis(30000) // <2> + .build(); + +sniffOnFailureListener.setSniffer(sniffer); // <3> +``` + +1. Set the failure listener to the `RestClient` instance +2. When sniffing on failure, not only do the nodes get updated after each failure, but an additional sniffing round is also scheduled sooner than usual, by default one minute after the failure, assuming that things will go back to normal and we want to detect that as soon as possible. Said interval can be customized at `Sniffer` creation time through the `setSniffAfterFailureDelayMillis` method. Note that this last configuration parameter has no effect in case sniffing on failure is not enabled like explained above. +3. Set the `Sniffer` instance to the failure listener + + +The Elasticsearch Nodes Info api doesn’t return the protocol to use when connecting to the nodes but only their `host:port` key-pair, hence `http` is used by default. In case `https` should be used instead, the `ElasticsearchNodesSniffer` instance has to be manually created and provided as follows: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniffer-https +```java +Rest5Client restClient = Rest5Client.builder( + new HttpHost("localhost", 9200)) + .build(); + +NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer( + restClient, + ElasticsearchNodesSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT, + ElasticsearchNodesSniffer.Scheme.HTTPS +); + +Sniffer sniffer = Sniffer.builder(restClient) + .setNodesSniffer(nodesSniffer).build(); +``` + +In the same way it is also possible to customize the `sniffRequestTimeout`, which defaults to one second. That is the `timeout` parameter provided as a query string parameter when calling the Nodes Info api, so that when the timeout expires on the server side, a valid response is still returned although it may contain only a subset of the nodes that are part of the cluster, the ones that have responded until then. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=sniff-request-timeout +```java +Rest5Client restClient = Rest5Client.builder( + new HttpHost("localhost", 9200)) + .build(); + +NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer( + restClient, + TimeUnit.SECONDS.toMillis(5), + ElasticsearchNodesSniffer.Scheme.HTTP +); + +Sniffer sniffer = Sniffer.builder(restClient) + .setNodesSniffer(nodesSniffer).build(); +``` + +Also, a custom `NodesSniffer` implementation can be provided for advanced use cases that may require fetching the nodes from external sources rather than from Elasticsearch: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/SnifferDocumentation.java tag=custom-nodes-sniffer +```java +Rest5Client restClient = Rest5Client + .builder(URI.create("http://localhost:9200")) + .build(); + +NodesSniffer nodesSniffer = new NodesSniffer() { + @Override + public List sniff() throws IOException { + return null; // <1> + } +}; + +Sniffer sniffer = Sniffer.builder(restClient) + .setNodesSniffer(nodesSniffer).build(); +``` + +1. Fetch the hosts from the external source + + diff --git a/docs/reference/transport/rest5-client/usage/index.md b/docs/reference/transport/rest5-client/usage/index.md new file mode 100644 index 000000000..aa1bb6631 --- /dev/null +++ b/docs/reference/transport/rest5-client/usage/index.md @@ -0,0 +1,13 @@ + +# Getting started + +This section describes how to get started with the low-level REST client from getting the artifact to using it in an application. + + + + + + + + + diff --git a/docs/reference/transport/rest5-client/usage/initialization.md b/docs/reference/transport/rest5-client/usage/initialization.md new file mode 100644 index 000000000..363e9e21d --- /dev/null +++ b/docs/reference/transport/rest5-client/usage/initialization.md @@ -0,0 +1,96 @@ + +# Initialization + +A `Rest5Client` instance can be built through the corresponding `Rest5ClientBuilder` class, created via `Rest5Client#builder(...)` static method. The only required argument is one or more URLs or `HttpHost` that the client will communicate with as follows: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-init +```java +Rest5Client restClient = Rest5Client.builder( + URI.create("http://localhost:9200"), + URI.create("http://localhost:9201") +).build(); +``` + +The `RestClient` class is thread-safe and ideally has the same lifecycle as the application that uses it. It is important that it gets closed when no longer needed so that all the resources used by it get properly released, as well as the underlying http client instance and its threads: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-close +```java +restClient.close(); +``` + +`RestClientBuilder` also allows to optionally set the following configuration parameters while building the `RestClient` instance: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-init-default-headers +```java +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + +Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")}; +builder.setDefaultHeaders(defaultHeaders); // <1> +``` + +1. Set the default headers that need to be sent with each request, to prevent having to specify them with each single request + + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-init-failure-listener +```java +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + +builder.setFailureListener(new Rest5Client.FailureListener() { + @Override + public void onFailure(Node node) { + // <1> + } +}); +``` + +1. Set a listener that gets notified every time a node fails, in case actions need to be taken. Used internally when sniffing on failure is enabled. + + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-init-node-selector +```java +Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + +builder.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS); // <1> +``` + +1. Set the node selector to be used to filter the nodes the client will send requests to among the ones that are set to the client itself. This is useful for instance to prevent sending requests to dedicated master nodes when sniffing is enabled. By default the client sends requests to every configured node. + + + + diff --git a/docs/reference/transport/rest5-client/usage/logging.md b/docs/reference/transport/rest5-client/usage/logging.md new file mode 100644 index 000000000..28a79acd8 --- /dev/null +++ b/docs/reference/transport/rest5-client/usage/logging.md @@ -0,0 +1,9 @@ +# Logging + +The Java REST 5 client uses the same logging library that the Apache Async Http Client uses: [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/), which comes with support for a number of popular logging implementations. The java packages to enable logging for is `co.elastic.clients.transport.rest5_client`. + +The request tracer logging can also be enabled to log every request and corresponding response in curl format. That comes handy when debugging, for instance in case a request needs to be manually executed to check whether it still yields the same response as it did. Enable trace logging for the `co.elastic.clients.transport.rest5_client.Request` class to have such log lines printed out. Do note that this type of logging is expensive and should not be enabled at all times in production environments, but rather temporarily used only when needed. + + + + diff --git a/docs/reference/transport/rest5-client/usage/requests.md b/docs/reference/transport/rest5-client/usage/requests.md new file mode 100644 index 000000000..a7ba8a3d3 --- /dev/null +++ b/docs/reference/transport/rest5-client/usage/requests.md @@ -0,0 +1,191 @@ + +# Performing requests + +Once the `RestClient` has been created, requests can be sent by calling either `performRequest` or `performRequestAsync`. `performRequest` is synchronous and will block the calling thread and return the `Response` when the request is successful or throw an exception if it fails. `performRequestAsync` is asynchronous and accepts a `ResponseListener` argument that it calls with a `Response` when the request is successful or with an `Exception` if it fails. + +This is synchronous: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-sync +```java +Request request = new Request( + "GET", // <1> + "/"); // <2> + +Response response = restClient.performRequest(request); +``` + +1. The HTTP method (`GET`, `POST`, `HEAD`, etc) +2. The endpoint on the server + + +And this is asynchronous: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-async +```java +Request request = new Request( + "GET", // <1> + "/"); // <2> + +Cancellable cancellable = restClient.performRequestAsync( + request, + new ResponseListener() { + @Override + public void onSuccess(Response response) { + // <3> + } + + @Override + public void onFailure(Exception exception) { + // <4> + } + }); +``` + +1. The HTTP method (`GET`, `POST`, `HEAD`, etc) +2. The endpoint on the server +3. Handle the response +4. Handle the failure + + +You can add request parameters to the request object: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-parameters +```java +request.addParameter("pretty", "true"); +``` + +You can set the body of the request to any `HttpEntity`: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-body +```java +request.setEntity(new StringEntity( + "{\"json\":\"text\"}", + ContentType.APPLICATION_JSON)); +``` + +::::{important} +The `ContentType` specified for the `HttpEntity` is important because it will be used to set the `Content-Type` header so that Elasticsearch can properly parse the content. +:::: + + +You can also set it to a `String` which will default to a `ContentType` of `application/json`. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-body-shorter +```java +request.setJsonEntity("{\"json\":\"text\"}"); +``` + +## RequestOptions [java-rest-low-usage-request-options] + +The `RequestOptions` class holds parts of the request that should be shared between many requests in the same application. You can make a singleton instance and share it between all requests: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-options-singleton +```java +private static final RequestOptions COMMON_OPTIONS; + +static { + RequestOptions.Builder builder = RequestOptions.DEFAULT + .toBuilder() + .addHeader("Authorization", "Bearer " + TOKEN) // <1> + .setHttpAsyncResponseConsumerFactory( // <2> + HttpAsyncResponseConsumerFactory.DEFAULT + ); + + COMMON_OPTIONS = builder.build(); +} +``` + +1. Add any headers needed by all requests. +2. Customize the response consumer. + + +`addHeader` is for headers that are required for authorization or to work with a proxy in front of Elasticsearch. There is no need to set the `Content-Type` header because the client will automatically set that from the `HttpEntity` attached to the request. + +You can set the `NodeSelector` which controls which nodes will receive requests. `NodeSelector.SKIP_DEDICATED_MASTERS` is a good choice. + +You can also customize the response consumer used to buffer the asynchronous responses. The default consumer will buffer up to 100MB of response on the JVM heap. If the response is larger then the request will fail. You could, for example, lower the maximum size which might be useful if you are running in a heap constrained environment like the example above. + +Once you’ve created the singleton you can use it when making requests: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-options-set-singleton +```java +request.setOptions(COMMON_OPTIONS); +``` + +You can also customize these options on a per request basis. For example, this adds an extra header: + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-options-customize-header +```java +RequestOptions.Builder options = COMMON_OPTIONS.toBuilder(); +options.addHeader("cats", "knock things off of other things"); +request.setOptions(options); +``` + + +## Multiple parallel asynchronous actions + +The client is quite happy to execute many actions in parallel. The following example indexes many documents in parallel. In a real world scenario you’d probably want to use the `_bulk` API instead, but the example is illustrative. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-async-example +```java +final CountDownLatch latch = new CountDownLatch(documents.length); +for (int i = 0; i < documents.length; i++) { + Request request = new Request("PUT", "/posts/doc/" + i); + //let's assume that the documents are stored in an HttpEntity array + request.setEntity(documents[i]); + restClient.performRequestAsync( + request, + new ResponseListener() { + @Override + public void onSuccess(Response response) { + // <1> + latch.countDown(); + } + + @Override + public void onFailure(Exception exception) { + // <2> + latch.countDown(); + } + } + ); +} +latch.await(); +``` + +1. Process the returned response +2. Handle the returned exception, due to communication error or a response with status code that indicates an error + + + +## Cancelling asynchronous requests + +The `performRequestAsync` method returns a `Cancellable` that exposes a single public method called `cancel`. Such method can be called to cancel the on-going request. Cancelling a request will result in aborting the http request through the underlying http client. On the server side, this does not automatically translate to the execution of that request being cancelled, which needs to be specifically implemented in the API itself. + +The use of the `Cancellable` instance is optional and you can safely ignore this if you don’t need it. A typical usecase for this would be using this together with frameworks like Rx Java or the Kotlin’s `suspendCancellableCoRoutine`. Cancelling no longer needed requests is a good way to avoid putting unnecessary load on Elasticsearch. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-async-cancel +```java +Request request = new Request("GET", "/posts/_search"); +Cancellable cancellable = restClient.performRequestAsync( + request, + new ResponseListener() { + @Override + public void onSuccess(Response response) { + // <1> + } + + @Override + public void onFailure(Exception exception) { + // <2> + } + } +); +cancellable.cancel(); +``` + +1. Process the returned response, in case it was ready before the request got cancelled +2. Handle the returned exception, which will most likely be a `CancellationException` as the request got cancelled + + + diff --git a/docs/reference/transport/rest5-client/usage/responses.md b/docs/reference/transport/rest5-client/usage/responses.md new file mode 100644 index 000000000..566dc63b6 --- /dev/null +++ b/docs/reference/transport/rest5-client/usage/responses.md @@ -0,0 +1,43 @@ + +# Reading responses + +The `Response` object, either returned by the synchronous `performRequest` methods or received as an argument in `ResponseListener#onSuccess(Response)`, wraps the response object returned by the http client and exposes some additional information. + +% :::{include-code} src={{doc-tests-src}}/rest5_client/RestClientDocumentation.java tag=rest-client-response2 +```java +Response response = restClient.performRequest(new Request("GET", "/")); + +RequestLine requestLine = response.getRequestLine(); // <1> +HttpHost host = response.getHost(); // <2> +int statusCode = response.getStatusCode(); // <3> +Header[] headers = response.getHeaders(); // <4> +String responseBody = EntityUtils.toString(response.getEntity()); // <5> +``` + +1. Information about the performed request +2. The host that returned the response +3. The response status line, from which you can for instance retrieve the status code +4. The response headers, which can also be retrieved by name though `getHeader(String)` +5. The response body enclosed in an [`org.apache.http.HttpEntity`](https://hc.apache.org/httpcomponents-core-4.4.x/current/httpcore/apidocs/org/apache/http/HttpEntity.html) object + + +When performing a request, an exception is thrown (or received as an argument in `ResponseListener#onFailure(Exception)` in the following scenarios: + +`IOException` +: communication problem (e.g. SocketTimeoutException) + +`ResponseException` +: a response was returned, but its status code indicated an error (not `2xx`). A `ResponseException` originates from a valid http response, hence it exposes its corresponding `Response` object which gives access to the returned response. + +::::{note} +A `ResponseException` is **not** thrown for `HEAD` requests that return a `404` status code because it is an expected `HEAD` response that simply denotes that the resource is not found. All other HTTP methods (e.g., `GET`) throw a `ResponseException` for `404` responses unless the `ignore` parameter contains `404`. `ignore` is a special client parameter that doesn’t get sent to Elasticsearch and contains a comma separated list of error status codes. It allows to control whether some error status code should be treated as an expected response rather than as an exception. This is useful for instance with the get api as it can return `404` when the document is missing, in which case the response body will not contain an error but rather the usual get api response, just without the document as it was not found. +:::: + + +Note that the low-level client doesn’t expose any helper for json marshalling and un-marshalling. Users are free to use the library that they prefer for that purpose. + +The underlying Apache Async Http Client ships with different `HttpEntity` implementations that allow to provide the request body in different formats (stream, byte array, string etc.). As for reading the response body, the `HttpEntity#getContent` method comes handy which returns an `InputStream` reading from the previously buffered response body. As an alternative, it is possible to provide a custom `HttpAsyncResponseConsumer` that controls how bytes are read and buffered. + + diff --git a/java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/documentation/RestClientDocumentation.java b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java similarity index 80% rename from java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/documentation/RestClientDocumentation.java rename to java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java index e9c030f7e..042265d5f 100644 --- a/java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/documentation/RestClientDocumentation.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java @@ -17,7 +17,7 @@ * under the License. */ -package co.elastic.clients.transport.rest5_client.low_level.documentation; +package co.elastic.clients.documentation.rest5_client; import co.elastic.clients.transport.rest5_client.low_level.Cancellable; import co.elastic.clients.transport.rest5_client.low_level.HttpAsyncResponseConsumerFactory; @@ -51,6 +51,7 @@ import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -85,25 +86,29 @@ public class RestClientDocumentation { private static final String TOKEN = "DUMMY"; - // tag::rest-client-options-singleton + //tag::rest-client-options-singleton private static final RequestOptions COMMON_OPTIONS; static { - RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); - builder.addHeader("Authorization", "Bearer " + TOKEN); // <1> - builder.setHttpAsyncResponseConsumerFactory( // <2> - HttpAsyncResponseConsumerFactory.DEFAULT); + RequestOptions.Builder builder = RequestOptions.DEFAULT + .toBuilder() + .addHeader("Authorization", "Bearer " + TOKEN) // <1> + .setHttpAsyncResponseConsumerFactory( // <2> + HttpAsyncResponseConsumerFactory.DEFAULT + ); + COMMON_OPTIONS = builder.build(); } - // end::rest-client-options-singleton + //end::rest-client-options-singleton @SuppressWarnings("unused") public void usage() throws IOException, InterruptedException, ParseException { //tag::rest-client-init Rest5Client restClient = Rest5Client.builder( - new HttpHost("http", "localhost", 9200), - new HttpHost("http", "localhost", 9201)).build(); + URI.create("http://localhost:9200"), + URI.create("http://localhost:9201") + ).build(); //end::rest-client-init //tag::rest-client-close @@ -112,24 +117,26 @@ public void usage() throws IOException, InterruptedException, ParseException { { //tag::rest-client-init-default-headers - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("http", "localhost", 9200)); + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")}; builder.setDefaultHeaders(defaultHeaders); // <1> //end::rest-client-init-default-headers } { //tag::rest-client-init-node-selector - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("http", "localhost", 9200)); + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + builder.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS); // <1> //end::rest-client-init-node-selector } { //tag::rest-client-init-allocation-aware-selector - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("http", "localhost", 9200)); - // <1> + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + builder.setNodeSelector(nodes -> { // <1> /* * Prefer any node that belongs to rack_one. If none is around @@ -149,7 +156,7 @@ public void usage() throws IOException, InterruptedException, ParseException { while (nodesIt.hasNext()) { Node node = nodesIt.next(); String rackId = node.getAttributes().get("rack_id").get(0); - if ("rack_one".equals(rackId) == false) { + if (!"rack_one".equals(rackId)) { nodesIt.remove(); } } @@ -159,8 +166,9 @@ public void usage() throws IOException, InterruptedException, ParseException { } { //tag::rest-client-init-failure-listener - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("http", "localhost", 9200)); + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)); + builder.setFailureListener(new Rest5Client.FailureListener() { @Override public void onFailure(Node node) { @@ -179,9 +187,10 @@ public void onFailure(Node node) { CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() .setConnectionManager(connectionManager) .build(); - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("http", "localhost", 9200)); - builder.setHttpClient(httpclient); + + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("http", "localhost", 9200)) + .setHttpClient(httpclient); //end::rest-client-init-request-config-callback } @@ -190,6 +199,7 @@ public void onFailure(Node node) { Request request = new Request( "GET", // <1> "/"); // <2> + Response response = restClient.performRequest(request); //end::rest-client-sync } @@ -198,7 +208,9 @@ public void onFailure(Node node) { Request request = new Request( "GET", // <1> "/"); // <2> - Cancellable cancellable = restClient.performRequestAsync(request, + + Cancellable cancellable = restClient.performRequestAsync( + request, new ResponseListener() { @Override public void onSuccess(Response response) { @@ -214,17 +226,21 @@ public void onFailure(Exception exception) { } { Request request = new Request("GET", "/"); + //tag::rest-client-parameters request.addParameter("pretty", "true"); //end::rest-client-parameters + //tag::rest-client-body request.setEntity(new StringEntity( "{\"json\":\"text\"}", ContentType.APPLICATION_JSON)); //end::rest-client-body + //tag::rest-client-body-shorter request.setJsonEntity("{\"json\":\"text\"}"); //end::rest-client-body-shorter + //tag::rest-client-options-set-singleton request.setOptions(COMMON_OPTIONS); //end::rest-client-options-set-singleton @@ -287,6 +303,7 @@ public void onFailure(Exception exception) { { //tag::rest-client-response2 Response response = restClient.performRequest(new Request("GET", "/")); + RequestLine requestLine = response.getRequestLine(); // <1> HttpHost host = response.getHost(); // <2> int statusCode = response.getStatusCode(); // <3> @@ -307,8 +324,8 @@ public void commonConfiguration() throws Exception { .setDefaultRequestConfig(requestConfigBuilder.build()) .build(); - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("localhost", 9200)) + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("localhost", 9200)) .setHttpClient(httpclient); //end::rest-client-config-timeouts } @@ -318,6 +335,7 @@ public void commonConfiguration() throws Exception { .setConnectTimeout(Timeout.ofMilliseconds(5000)) .setConnectionRequestTimeout(Timeout.ofMilliseconds(60000)) .build(); + RequestOptions options = RequestOptions.DEFAULT.toBuilder() .setRequestConfig(requestConfig) .build(); @@ -326,22 +344,23 @@ public void commonConfiguration() throws Exception { { //tag::rest-client-config-threads CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() - .setIOReactorConfig(IOReactorConfig.custom() - .setIoThreadCount(1).build()) + .setIOReactorConfig( + IOReactorConfig.custom().setIoThreadCount(1).build() + ) .build(); - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("localhost", 9200)) + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("localhost", 9200)) .setHttpClient(httpclient); //end::rest-client-config-threads } { //tag::rest-client-config-basic-auth + var creds = Base64.getEncoder() + .encodeToString("user:test-user-password".getBytes()); - var creds = Base64.getEncoder().encodeToString("user:test-user-password".getBytes()); - - Rest5ClientBuilder restClient = Rest5Client.builder(new HttpHost("https", "localhost", - 9200)) + Rest5ClientBuilder restClient = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) .setDefaultHeaders(new Header[]{ new BasicHeader("Authorization", "Basic " + creds) }); @@ -355,11 +374,11 @@ public void commonConfiguration() throws Exception { var creds = Base64.getEncoder().encodeToString("user:test-user-password".getBytes()); CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() - .disableAuthCaching() + .disableAuthCaching() // <1> .build(); - Rest5ClientBuilder restClient = Rest5Client.builder(new HttpHost("https", "localhost", - 9200)) + Rest5ClientBuilder restClient = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) .setHttpClient(httpclient) .setDefaultHeaders(new Header[]{ new BasicHeader("Authorization", "Basic " + creds) @@ -374,33 +393,35 @@ public void commonConfiguration() throws Exception { try (InputStream is = Files.newInputStream(trustStorePath)) { truststore.load(is, keyStorePass.toCharArray()); } - SSLContextBuilder sslBuilder = SSLContexts.custom() - .loadTrustMaterial(truststore, null); - final SSLContext sslContext = sslBuilder.build(); - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("https", "localhost", - 9200)) + + SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(truststore, null) + .build(); + + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) .setSSLContext(sslContext); //end::rest-client-config-encrypted-communication } { //tag::rest-client-config-trust-ca-pem Path caCertificatePath = Paths.get("/path/to/ca.crt"); - CertificateFactory factory = - CertificateFactory.getInstance("X.509"); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); Certificate trustedCa; try (InputStream is = Files.newInputStream(caCertificatePath)) { trustedCa = factory.generateCertificate(is); } + KeyStore trustStore = KeyStore.getInstance("pkcs12"); trustStore.load(null, null); trustStore.setCertificateEntry("ca", trustedCa); - SSLContextBuilder sslContextBuilder = SSLContexts.custom() - .loadTrustMaterial(trustStore, null); - final SSLContext sslContext = sslContextBuilder.build(); - Rest5Client.builder( - new HttpHost("https", "localhost", - 9200)) + + SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(trustStore, null) + .build(); + + Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) .setSSLContext(sslContext); //end::rest-client-config-trust-ca-pem } @@ -412,30 +433,35 @@ public void commonConfiguration() throws Exception { Path keyStorePath = Paths.get("/path/to/your/keystore.p12"); KeyStore trustStore = KeyStore.getInstance("pkcs12"); KeyStore keyStore = KeyStore.getInstance("pkcs12"); + try (InputStream is = Files.newInputStream(trustStorePath)) { trustStore.load(is, trustStorePass.toCharArray()); } + try (InputStream is = Files.newInputStream(keyStorePath)) { keyStore.load(is, keyStorePass.toCharArray()); } - SSLContextBuilder sslBuilder = SSLContexts.custom() + + SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(trustStore, null) - .loadKeyMaterial(keyStore, keyStorePass.toCharArray()); - final SSLContext sslContext = sslBuilder.build(); - Rest5Client.builder( - new HttpHost("https", "localhost", - 9200)) + .loadKeyMaterial(keyStore, keyStorePass.toCharArray()) + .build(); + + Rest5Client + .builder(new HttpHost("https", "localhost", 9200)) .setSSLContext(sslContext); //end::rest-client-config-mutual-tls-authentication } { //tag::rest-client-auth-bearer-token - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("https", "localhost", - 9200)); - Header[] defaultHeaders = - new Header[]{new BasicHeader("Authorization", - "Bearer u6iuAxZ0RG1Kcm5jVFI4eU4tZU9aVFEwT2F3")}; + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)); + + Header[] defaultHeaders = new Header[]{ new BasicHeader( + "Authorization", + "Bearer u6iuAxZ0RG1Kcm5jVFI4eU4tZU9aVFEwT2F3" + )}; + builder.setDefaultHeaders(defaultHeaders); //end::rest-client-auth-bearer-token } @@ -443,19 +469,20 @@ public void commonConfiguration() throws Exception { //tag::rest-client-auth-api-key String apiKeyId = "uqlEyn8B_gQ_jlvwDIvM"; String apiKeySecret = "HxHWk2m4RN-V_qg9cDpuX"; - String apiKeyAuth = - Base64.getEncoder().encodeToString( - (apiKeyId + ":" + apiKeySecret) - .getBytes(StandardCharsets.UTF_8)); - Rest5ClientBuilder builder = Rest5Client.builder( - new HttpHost("https", "localhost", - 9200)); - Header[] defaultHeaders = - new Header[]{new BasicHeader("Authorization", - "ApiKey " + apiKeyAuth)}; + String apiKeyAuth = Base64.getEncoder().encodeToString( + (apiKeyId + ":" + apiKeySecret).getBytes(StandardCharsets.UTF_8) + ); + + Rest5ClientBuilder builder = Rest5Client + .builder(new HttpHost("https", "localhost", 9200)); + + Header[] defaultHeaders = new Header[]{ new BasicHeader( + "Authorization", + "ApiKey " + apiKeyAuth + )}; + builder.setDefaultHeaders(defaultHeaders); //end::rest-client-auth-api-key } - } } diff --git a/java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/sniffer/documentation/SnifferDocumentation.java b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/SnifferDocumentation.java similarity index 85% rename from java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/sniffer/documentation/SnifferDocumentation.java rename to java-client/src/test/java/co/elastic/clients/documentation/rest5_client/SnifferDocumentation.java index 995d16670..3db0bd7a1 100644 --- a/java-client/src/test/java/co/elastic/clients/transport/rest5_client/low_level/sniffer/documentation/SnifferDocumentation.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/SnifferDocumentation.java @@ -17,7 +17,7 @@ * under the License. */ -package co.elastic.clients.transport.rest5_client.low_level.sniffer.documentation; +package co.elastic.clients.documentation.rest5_client; import org.apache.hc.core5.http.HttpHost; import co.elastic.clients.transport.rest5_client.low_level.Node; @@ -28,6 +28,7 @@ import co.elastic.clients.transport.rest5_client.low_level.sniffer.Sniffer; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.TimeUnit; @@ -56,9 +57,10 @@ public class SnifferDocumentation { public void usage() throws IOException, URISyntaxException { { //tag::sniffer-init - Rest5Client restClient = Rest5Client.builder( - HttpHost.create("http://localhost:9200")) + Rest5Client restClient = Rest5Client + .builder(HttpHost.create("http://localhost:9200")) .build(); + Sniffer sniffer = Sniffer.builder(restClient).build(); //end::sniffer-init @@ -69,9 +71,10 @@ public void usage() throws IOException, URISyntaxException { } { //tag::sniffer-interval - Rest5Client restClient = Rest5Client.builder( - new HttpHost("localhost", 9200)) + Rest5Client restClient = Rest5Client + .builder(new HttpHost("localhost", 9200)) .build(); + Sniffer sniffer = Sniffer.builder(restClient) .setSniffIntervalMillis(60000).build(); //end::sniffer-interval @@ -80,13 +83,16 @@ public void usage() throws IOException, URISyntaxException { //tag::sniff-on-failure SniffOnFailureListener sniffOnFailureListener = new SniffOnFailureListener(); - Rest5Client restClient = Rest5Client.builder( - new HttpHost("localhost", 9200)) + + Rest5Client restClient = Rest5Client + .builder(new HttpHost("localhost", 9200)) .setFailureListener(sniffOnFailureListener) // <1> .build(); + Sniffer sniffer = Sniffer.builder(restClient) .setSniffAfterFailureDelayMillis(30000) // <2> .build(); + sniffOnFailureListener.setSniffer(sniffer); // <3> //end::sniff-on-failure } @@ -95,10 +101,13 @@ public void usage() throws IOException, URISyntaxException { Rest5Client restClient = Rest5Client.builder( new HttpHost("localhost", 9200)) .build(); + NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer( restClient, ElasticsearchNodesSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT, - ElasticsearchNodesSniffer.Scheme.HTTPS); + ElasticsearchNodesSniffer.Scheme.HTTPS + ); + Sniffer sniffer = Sniffer.builder(restClient) .setNodesSniffer(nodesSniffer).build(); //end::sniffer-https @@ -108,25 +117,30 @@ public void usage() throws IOException, URISyntaxException { Rest5Client restClient = Rest5Client.builder( new HttpHost("localhost", 9200)) .build(); + NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer( restClient, TimeUnit.SECONDS.toMillis(5), - ElasticsearchNodesSniffer.Scheme.HTTP); + ElasticsearchNodesSniffer.Scheme.HTTP + ); + Sniffer sniffer = Sniffer.builder(restClient) .setNodesSniffer(nodesSniffer).build(); //end::sniff-request-timeout } { //tag::custom-nodes-sniffer - Rest5Client restClient = Rest5Client.builder( - HttpHost.create("http://localhost:9200")) + Rest5Client restClient = Rest5Client + .builder(URI.create("http://localhost:9200")) .build(); + NodesSniffer nodesSniffer = new NodesSniffer() { - @Override - public List sniff() throws IOException { - return null; // <1> - } - }; + @Override + public List sniff() throws IOException { + return null; // <1> + } + }; + Sniffer sniffer = Sniffer.builder(restClient) .setNodesSniffer(nodesSniffer).build(); //end::custom-nodes-sniffer diff --git a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java index aecd42b0e..899b20f5f 100644 --- a/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java +++ b/tools/src/main/java/co/elastic/clients/tools/docs/IncludeExpander.java @@ -185,14 +185,19 @@ public static void expandTaggedFile(String path, String tag, StringBuilder outpu String content = Files.readString(file.toPath()); var reader = new BufferedReader(new StringReader(content)); - String startTag = "//tag::" + tag; - String endTag = "//end::" + tag; + String startTag = "tag::" + tag; + String endTag = "end::" + tag; String line; - int pos; boolean found = false; while ((line = reader.readLine()) != null) { - if ((pos = line.indexOf(startTag)) >= 0) { + if (line.contains(startTag)) { + // Find indentation level + int start = 0; + while (Character.isWhitespace(line.charAt(start))) { + start++; + } + found = true; while((line = reader.readLine()) != null) { if (line.contains(endTag)) { @@ -200,8 +205,8 @@ public static void expandTaggedFile(String path, String tag, StringBuilder outpu } // If the line has more characters than the tag's initial position, // assume it's whitespace and truncate it to remove indentation. - if (line.length() > pos) { - line = line.substring(pos); + if (line.length() > start) { + line = line.substring(start); } output.append(line).append("\n"); } From a81fc00f29677cf4b0ec395eae76e17a8b87f507 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 16:15:05 +0200 Subject: [PATCH 10/14] Add the troubleshooting docs, misc cleanup --- docs/reference/getting-started.md | 19 +++----- docs/reference/setup/connecting.md | 10 ++--- docs/reference/setup/index.md | 7 ++- docs/reference/setup/installation.md | 4 +- docs/reference/toc.yml | 8 ++++ docs/reference/troubleshooting/index.md | 11 +++++ .../troubleshooting/io-reactor-errors.md | 35 +++++++++++++++ .../missing-required-property.md | 41 +++++++++++++++++ .../no-such-method-request-options.md | 33 ++++++++++++++ .../serialize-without-typed-keys.md | 45 +++++++++++++++++++ .../getting_started/ConnectingTest.java | 2 - 11 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 docs/reference/troubleshooting/index.md create mode 100644 docs/reference/troubleshooting/io-reactor-errors.md create mode 100644 docs/reference/troubleshooting/missing-required-property.md create mode 100644 docs/reference/troubleshooting/no-such-method-request-options.md create mode 100644 docs/reference/troubleshooting/serialize-without-typed-keys.md diff --git a/docs/reference/getting-started.md b/docs/reference/getting-started.md index 66ce28451..153f22ec4 100644 --- a/docs/reference/getting-started.md +++ b/docs/reference/getting-started.md @@ -3,21 +3,18 @@ mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/getting-started-java.html --- -# Getting started [getting-started-java] +# Getting started This page guides you through the installation process of the Java client, shows you how to instantiate the client, and how to perform basic Elasticsearch operations with it. +### Requirements -### Requirements [_requirements] +Java 17 or later. -* Java 17 or later. -* A JSON object mapping library to allow seamless integration of your application classes with the Elasticsearch API. The examples below show usage with Jackson, which is the default. +### Installation -### Installation [_installation] - - -#### Installation in a Gradle project by using Jackson [_installation_in_a_gradle_project_by_using_jackson] +#### Installation in a Gradle project ```groovy subs=true dependencies { @@ -26,7 +23,7 @@ dependencies { ``` -#### Installation in a Maven project by using Jackson [_installation_in_a_maven_project_by_using_jackson] +#### Installation in a Maven project In the `pom.xml` of your project, add the following repository definition and dependencies: @@ -47,7 +44,7 @@ In the `pom.xml` of your project, add the following repository definition and de Refer to the [Installation](setup/installation.md) page to learn more. -### Connecting [_connecting] +### Connecting You can connect to the Elastic Cloud using an API key and the Elasticsearch endpoint. @@ -60,8 +57,6 @@ String apiKey = "VnVhQ2ZHY0JDZGJrU..."; ElasticsearchClient esClient = ElasticsearchClient.of(b -> b .host(serverUrl) .apiKey(apiKey) - // Use the Jackson mapper to deserialize JSON to application objects - .jsonMapper(new JacksonJsonpMapper()) ); // Use the client... diff --git a/docs/reference/setup/connecting.md b/docs/reference/setup/connecting.md index 5b6be8b35..a8c3e5a4f 100644 --- a/docs/reference/setup/connecting.md +++ b/docs/reference/setup/connecting.md @@ -8,10 +8,10 @@ mapped_pages: The Java API Client is structured around three main components: * **API client classes**. These provide strongly typed data structures and methods for {{es}} APIs. Since the {{es}} API is large, it is structured in feature groups (also called “namespaces”), each having its own client class. {{es}} core features are implemented in the `ElasticsearchClient` class. -* **A JSON object mapper**. This maps your application classes to JSON and seamlessly integrates them with the API client. -* **A transport layer implementation**. This is where all HTTP request handling takes place. +* **A JSON object mapper**. This maps your application classes to JSON and seamlessly integrates them with the API client. The default implementation uses Jackson. +* **A transport layer implementation**. This is where all HTTP request handling takes place. The [default implementation](/reference/transport/rest5-client/index.md) is based on the [Apache http client library](https://hc.apache.org/). -This code snippet creates and wires together these three components: +This code snippet uses the default configurations and only needs the location and credentials to connect to Elasticsearch: % :::{include-code} src={{doc-tests-src}}/getting_started/ConnectingTest.java tag=create-client ```java @@ -22,8 +22,6 @@ String apiKey = "VnVhQ2ZHY0JDZGJrU..."; ElasticsearchClient esClient = ElasticsearchClient.of(b -> b .host(serverUrl) .apiKey(apiKey) - // Use the Jackson mapper to deserialize JSON to application objects - .jsonMapper(new JacksonJsonpMapper()) ); // Use the client... @@ -32,7 +30,7 @@ ElasticsearchClient esClient = ElasticsearchClient.of(b -> b esClient.close(); ``` -Authentication is managed by the [Java Low Level REST Client](/reference/transport/rest-client/index.md). For further details on configuring authentication, refer to [its documentation](/reference/transport/rest-client/config/basic_authentication.md). +Authentication is managed by the [Java Low Level REST Client](/reference/transport/rest5-client/index.md). For further details on configuring authentication, refer to [its documentation](/reference/transport/rest5-client/config/basic_authentication.md). ## Your first request [_your_first_request] diff --git a/docs/reference/setup/index.md b/docs/reference/setup/index.md index e0509009c..ede57f1a9 100644 --- a/docs/reference/setup/index.md +++ b/docs/reference/setup/index.md @@ -5,10 +5,9 @@ mapped_pages: # Setup [_setup] -* [Installation](installation.md) -* [Connecting](connecting.md) -* [Using OpenTelemetry](opentelemetry.md) -* [Java Low Level REST Client](/reference/transport/rest-client/index.md) +* [](installation.md) +* [](connecting.md) +* [](opentelemetry.md) diff --git a/docs/reference/setup/installation.md b/docs/reference/setup/installation.md index c26d29f35..a535d919e 100644 --- a/docs/reference/setup/installation.md +++ b/docs/reference/setup/installation.md @@ -13,7 +13,7 @@ Requirements: Releases are hosted on [Maven Central](https://search.maven.org/search?q=g:co.elastic.clients). If you are looking for a SNAPSHOT version, the Elastic Maven Snapshot repository is available at [https://snapshots.elastic.co/maven/](https://snapshots.elastic.co/maven/). -## Installation in a Gradle project by using Jackson [gradle] +## Installation in a Gradle project [gradle] ```groovy subs=true dependencies { @@ -22,7 +22,7 @@ dependencies { ``` -## Installation in a Maven project by using Jackson [maven] +## Installation in a Maven project [maven] In the `pom.xml` of your project, add the following repository definition and dependencies: diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml index 5885bac01..89d47d2df 100644 --- a/docs/reference/toc.yml +++ b/docs/reference/toc.yml @@ -35,6 +35,14 @@ toc: - file: aggregations.md - file: esql.md + - folder: troubleshooting + children: + - file: index.md + - file: missing-required-property.md + - file: no-such-method-request-options.md + - file: io-reactor-errors.md + - file: serialize-without-typed-keys.md + - folder: transport children: - file: index.md diff --git a/docs/reference/troubleshooting/index.md b/docs/reference/troubleshooting/index.md new file mode 100644 index 000000000..075859c10 --- /dev/null +++ b/docs/reference/troubleshooting/index.md @@ -0,0 +1,11 @@ +# Troubleshooting + +## Exceptions + +* [](missing-required-property.md) +* [](no-such-method-request-options.md) +* [](io-reactor-errors.md) + +## Miscellaneous + +* [](serialize-without-typed-keys.md) diff --git a/docs/reference/troubleshooting/io-reactor-errors.md b/docs/reference/troubleshooting/io-reactor-errors.md new file mode 100644 index 000000000..cd9614288 --- /dev/null +++ b/docs/reference/troubleshooting/io-reactor-errors.md @@ -0,0 +1,35 @@ +--- +navigation_title: IOReactor errors +--- + +# Apache http-client I/O reactor errors + +Sending requests can sometimes fail with one of the following errors, coming from the Apache http-client library: + +* `Request cannot be executed; I/O reactor status: STOPPED` +* `I/O reactor terminated abnormally` +* `I/O reactor has been shut down` + +The I/O Reactor is the internal event loop in the http client library. It can terminate when an application callback throws an `Error`, like an `OutOfMemoryError` or a `StackOverflowError`. Remember that `Error` is different from a regular `Exception` and – [quoting the Java documentation](https://docs.oracle.com/javase/8/docs/api/?java/lang/Error.md) – *indicates serious problems that a reasonable application should not try to catch*. + +In the context of the Elasticsearch Java clients, this can happen on two occasions: + +* the application calls the low level `RestClient` directly, using the asynchronous `performRequestAsync` method, and an `Error` is thrown in the `ResponseListener` provided by the application. +* an `OutOfMemoryError` happens while buffering the body of an http response. + +In the first case, it is the application’s responsibility to catch `Error` in its `ResponseListener` and decide what to do when these errors happen. + +The second case is taken care of in the Java API Client since version 8.12: the error is wrapped in a `RuntimeException` that is reported to the application. + +In previous versions of the Java API Client, you can copy/paste the `SafeResponseConsumer` class in your project and initialize the `RestClientTransport` as follows: + +```java +RestClient restClient = ... +JsonpMapper mapper = ... +RestClientOptions options = new RestClientOptions( + SafeResponseConsumer.DEFAULT_REQUEST_OPTIONS +); +RestClientTransport transport = new RestClientTransport( + restClient, mapper, options +); +``` diff --git a/docs/reference/troubleshooting/missing-required-property.md b/docs/reference/troubleshooting/missing-required-property.md new file mode 100644 index 000000000..3e5c17105 --- /dev/null +++ b/docs/reference/troubleshooting/missing-required-property.md @@ -0,0 +1,41 @@ +--- +navigation_title: Missing required property +--- + +# Missing required property exception in a response + +The Java API Client distinguishes optional and required properties. Optional properties are marked with the `@Nullable` annotation. + +When an API object is built and a required property hasn’t been set, a `MissingRequiredPropertyException` is thrown. This applies both to request object built by your application and to response objects returned by Elasticsearch, so that you can be assured that a property that does not have the `@Nullable` annotation will never be `null`. + +However, there may be bugs in the [Elasticsearch API specification](https://github.com/elastic/elasticsearch-specification) where a response object’s property is incorrectly required, leading to a `MissingRequiredPropertyException` when deserializing a response. If this happens, here’s how you can work around it: + +* Make sure you use the latest release of the Java API Client. The issue may already have been fixed. +* If the issue is still present on the latest version, [open an issue](https://github.com/elastic/elasticsearch-java/issues/new/choose) so that we can fix it in the next release. Please help us to improve the Java API Client. +* Temporarily disable required property checks for the offending request: + +::::{warning} +This is a workaround. Do not consider this as a permanent solution, and please [open an issue](https://github.com/elastic/elasticsearch-java/issues/new/choose) so that the problem can be fixed in a future release. +:::: + + +```java + ApiTypeHelper.DANGEROUS_disableRequiredPropertiesCheck(true); + SomeRequest request = SomeRequest.of(...); + SomeResponse response = esClient.someApi(request); + ApiTypeHelper.DANGEROUS_disableRequiredPropertiesCheck(false); + // Do something with response +``` + +The `DANGEROUS_disableRequiredPropertiesCheck` method disables required property checks on the current thread, and for response deserialization in asynchronous requests. As its name implies, it is dangerous as it removes the guarantees of properties that are not `@Nullable`. This is a temporary workaround until the issue is fixed. + +Note that the result of this method is an `AutoCloseable` object that resets required property checks to its previous setting. You can therefore use it in a try-with-resource block as follows: + +```java +try (ApiTypeHelper.DisabledChecksHandle h = + ApiTypeHelper.DANGEROUS_disableRequiredPropertiesCheck(true)) { + SomeRequest request = SomeRequest.of(...); + SomeResponse response = esClient.someApi(request); + // Do something with response +} +``` diff --git a/docs/reference/troubleshooting/no-such-method-request-options.md b/docs/reference/troubleshooting/no-such-method-request-options.md new file mode 100644 index 000000000..9d2c45fa8 --- /dev/null +++ b/docs/reference/troubleshooting/no-such-method-request-options.md @@ -0,0 +1,33 @@ +--- +navigation_title: NoSuchMethodError: removeHeader +--- + +# `NoSuchMethodError` when creating a client + +In certain contexts you may encounter an error when creating the `ElasticsearchClient` saying that the method `RequestOptions$Builder.removeHeader` does not exist: + +```java +java.lang.NoSuchMethodError: 'org.elasticsearch.client.RequestOptions$Builder org.elasticsearch.client.RequestOptions$Builder.removeHeader(java.lang.String)' +``` + +This method was introduced in `elasticsearch-rest-client` version 7.16.0. The error happens because your project is using an older version of this dependency. + +This happens in particular when the project is using the [Spring Boot Maven Plugin](https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/), as this plugin [defines versions for commonly used libraries](https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-dependencies/build.gradle), including `elasticsearch-rest-client`. Depending on the version of Spring Boot used in the project, that version may be outdated. + +To solve this issue, you have to add the `elasticsearch-rest-client` dependency explicitly in your project, with the same version as `elasticsearch-java` (see also [Installation](elasticsearch-java://reference/installation.md)). + +Using Gradle: + +```groovy +implementation 'org.elasticsearch.client:elasticsearch-rest-client:9.0.0-beta1' +``` + +Using Maven: + +```xml + + org.elasticsearch.client + elasticsearch-rest-client + 9.0.0-beta1 + +``` diff --git a/docs/reference/troubleshooting/serialize-without-typed-keys.md b/docs/reference/troubleshooting/serialize-without-typed-keys.md new file mode 100644 index 000000000..dd4754c8a --- /dev/null +++ b/docs/reference/troubleshooting/serialize-without-typed-keys.md @@ -0,0 +1,45 @@ +--- +navigation_title: Serializing without typed keys +--- + +# Serializing aggregations and suggestions without typed keys + +{{es}} search requests accept a `typed_key` parameter that allow returning type information along with the name in aggregation and suggestion results (see Elasticsearch's aggregation documentation for additional details). + +The Java API Client always adds this parameter to search requests, as type information is needed to know the concrete class that should be used to deserialize aggregation and suggestion results. + +Symmetrically, the Java API Client also serializes aggregation and suggestion results using this `typed_keys` format, so that it can correctly deserialize the results of its own serialization. + +% :::{include-code} src={{doc-tests-src}}/troubleshooting/TroubleShootingTests.java tag=aggregation-typed-keys +```java +JsonpMapper mapper = esClient._jsonpMapper(); + +StringWriter writer = new StringWriter(); +try (JsonGenerator generator = mapper.jsonProvider().createGenerator(writer)) { + mapper.serialize(searchResponse, generator); +} +String result = writer.toString(); + +// The aggregation property provides the "avg" type and "price" name +assertTrue(result.contains("\"aggregations\":{\"avg#price\":{\"value\":3.14}}}")); +``` + +However, in some use cases serializing objects in the `typed_keys` format may not be desirable, for example when the Java API Client is used in an application that acts as a front-end to other services that expect the default format for aggregations and suggestions. + +You can disable `typed_keys` serialization by setting the `JsonpMapperFeatures.SERIALIZE_TYPED_KEYS` attribute to `false` on your mapper object: + +% :::{include-code} src={{doc-tests-src}}/troubleshooting/TroubleShootingTests.java tag=aggregation-no-typed-keys +```java +// Create a new mapper with the typed_keys feature disabled +JsonpMapper mapper = esClient._jsonpMapper() + .withAttribute(JsonpMapperFeatures.SERIALIZE_TYPED_KEYS, false); + +StringWriter writer = new StringWriter(); +try (JsonGenerator generator = mapper.jsonProvider().createGenerator(writer)) { + mapper.serialize(searchResponse, generator); +} +String result = writer.toString(); + +// The aggregation only provides the "price" name +assertTrue(result.contains("\"aggregations\":{\"price\":{\"value\":3.14}}}")); +``` diff --git a/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java b/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java index b8fef1ecf..45cec778b 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java @@ -47,8 +47,6 @@ public void createClient() throws Exception { ElasticsearchClient esClient = ElasticsearchClient.of(b -> b .host(serverUrl) .apiKey(apiKey) - // Use the Jackson mapper to deserialize JSON to application objects - .jsonMapper(new JacksonJsonpMapper()) ); // Use the client... From 146e32e281238914f2c07b95a8056022f0506a3f Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 19:11:44 +0200 Subject: [PATCH 11/14] Transport docs --- docs/reference/transport/index.md | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/reference/transport/index.md b/docs/reference/transport/index.md index 291983004..afcd049ab 100644 --- a/docs/reference/transport/index.md +++ b/docs/reference/transport/index.md @@ -1 +1,33 @@ -# HTTP transport +--- +navigation_title: Transport layer +--- + +# The transport layer + +`ElasticsearchClient` exposes the Elasticsearch APIs using high level objects and data structures. It relies on a `Transport` implementation for lower level concerns that include: + +* JSON serialization and deserialization, by delegating to a `JsonpMapper`. +* network communication: HTTP, TLS, load-balancing among cluster nodes, etc. + +## JSON mappers + +The Java API client comes with two JSON mapping implementations: + +* `JacksonJsonpMapper`, the default implementation based on the popular [Jackson](https://github.com/FasterXML/jackson) library. +* `JsonbJsonpMapper`, based on the JakartaEE JSONP specification, which allows using any implementation of this specification such as [Eclipse Parsson](https://github.com/eclipse-ee4j/parsson) + +Which implementation should you use? + +Unless you have specific requirements, use the default Jackson implementation. + +## HTTP implementations + +The Java API client comes with two HTTP implementations: + +* [](rest5-client/index.md), the default implementation, based on the Apache http client library version 5. It's a direct port of the previous version to this new library. +* [Rest Client](rest-client/index.md), the legacy implementation that has existed since Elasticsearch version 7, based on the Apache http client library version 4. + +Which implementation should you use? +* if you're starting a new application, use the default Rest 5 Client. +* if you're upgrading an existing application, consider upgrading to Rest 5 Client. Adapting your client creation code to the newer Apache http library should be releatively straightforward. +* if you're upgrading an application with a heavily customized Rest Client configuration, then consider staying with this legacy version. Be mindful however that it may be removed in the next major release. From 8eedcd624e3ac9bfbc494f4ac1e01cfc9d9bec82 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 19:18:11 +0200 Subject: [PATCH 12/14] Checkstyle --- .../clients/documentation/getting_started/ConnectingTest.java | 1 - .../documentation/rest5_client/RestClientDocumentation.java | 1 - 2 files changed, 2 deletions(-) diff --git a/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java b/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java index 45cec778b..1d779b30c 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/getting_started/ConnectingTest.java @@ -23,7 +23,6 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.TransportUtils; import co.elastic.clients.transport.instrumentation.OpenTelemetryForElasticsearch; import io.opentelemetry.api.OpenTelemetry; diff --git a/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java index 042265d5f..fb8756033 100644 --- a/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java +++ b/java-client/src/test/java/co/elastic/clients/documentation/rest5_client/RestClientDocumentation.java @@ -44,7 +44,6 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.RequestLine; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.Timeout; From 4453b557070c8b996f998e45cea652481cb6f73b Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Wed, 9 Apr 2025 19:26:41 +0200 Subject: [PATCH 13/14] Fix link --- .../no-such-method-request-options.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/troubleshooting/no-such-method-request-options.md b/docs/reference/troubleshooting/no-such-method-request-options.md index 9d2c45fa8..2339a9a03 100644 --- a/docs/reference/troubleshooting/no-such-method-request-options.md +++ b/docs/reference/troubleshooting/no-such-method-request-options.md @@ -1,5 +1,5 @@ --- -navigation_title: NoSuchMethodError: removeHeader +navigation_title: "NoSuchMethodError: removeHeader" --- # `NoSuchMethodError` when creating a client @@ -14,20 +14,20 @@ This method was introduced in `elasticsearch-rest-client` version 7.16.0. The er This happens in particular when the project is using the [Spring Boot Maven Plugin](https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/), as this plugin [defines versions for commonly used libraries](https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-dependencies/build.gradle), including `elasticsearch-rest-client`. Depending on the version of Spring Boot used in the project, that version may be outdated. -To solve this issue, you have to add the `elasticsearch-rest-client` dependency explicitly in your project, with the same version as `elasticsearch-java` (see also [Installation](elasticsearch-java://reference/installation.md)). +To solve this issue, you have to add the `elasticsearch-rest-client` dependency explicitly in your project, with the same version as `elasticsearch-java` (see also [Installation](/reference/setup/installation.md)). Using Gradle: -```groovy -implementation 'org.elasticsearch.client:elasticsearch-rest-client:9.0.0-beta1' +```groovy subs=true +implementation 'org.elasticsearch.client:elasticsearch-rest-client:{{version}}' ``` Using Maven: -```xml +```xml subs=true org.elasticsearch.client elasticsearch-rest-client - 9.0.0-beta1 + {{version}} ``` From d8c25beb06e539d45c9e74636662abe3ea155287 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Thu, 10 Apr 2025 08:43:35 +0200 Subject: [PATCH 14/14] Re-add "mapped-pages" to troubleshooting docs --- docs/docset.yml | 2 -- docs/reference/troubleshooting/index.md | 5 +++++ docs/reference/troubleshooting/io-reactor-errors.md | 2 ++ docs/reference/troubleshooting/missing-required-property.md | 2 ++ .../troubleshooting/no-such-method-request-options.md | 2 ++ .../troubleshooting/serialize-without-typed-keys.md | 2 ++ 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/docset.yml b/docs/docset.yml index 51e45c445..73b405a45 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -9,8 +9,6 @@ toc: - toc: reference - toc: release-notes subs: - serverless-full: "Elastic Cloud Serverless" - es3: "Elasticsearch Serverless" es: "Elasticsearch" version: "9.0.0" # Not used by docs-builder, but present in `% :::{include-bloc}` preprocessing directives (see IncludeExpander.java) diff --git a/docs/reference/troubleshooting/index.md b/docs/reference/troubleshooting/index.md index 075859c10..9922f95d4 100644 --- a/docs/reference/troubleshooting/index.md +++ b/docs/reference/troubleshooting/index.md @@ -1,3 +1,8 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/troubleshooting.html +--- + # Troubleshooting ## Exceptions diff --git a/docs/reference/troubleshooting/io-reactor-errors.md b/docs/reference/troubleshooting/io-reactor-errors.md index cd9614288..9315cca17 100644 --- a/docs/reference/troubleshooting/io-reactor-errors.md +++ b/docs/reference/troubleshooting/io-reactor-errors.md @@ -1,5 +1,7 @@ --- navigation_title: IOReactor errors +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/io-reactor-errors.html --- # Apache http-client I/O reactor errors diff --git a/docs/reference/troubleshooting/missing-required-property.md b/docs/reference/troubleshooting/missing-required-property.md index 3e5c17105..b9f1070a9 100644 --- a/docs/reference/troubleshooting/missing-required-property.md +++ b/docs/reference/troubleshooting/missing-required-property.md @@ -1,5 +1,7 @@ --- navigation_title: Missing required property +mapped_pages: +- https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/missing-required-property.html --- # Missing required property exception in a response diff --git a/docs/reference/troubleshooting/no-such-method-request-options.md b/docs/reference/troubleshooting/no-such-method-request-options.md index 2339a9a03..db80a52f1 100644 --- a/docs/reference/troubleshooting/no-such-method-request-options.md +++ b/docs/reference/troubleshooting/no-such-method-request-options.md @@ -1,5 +1,7 @@ --- navigation_title: "NoSuchMethodError: removeHeader" +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/no-such-method-request-options.html --- # `NoSuchMethodError` when creating a client diff --git a/docs/reference/troubleshooting/serialize-without-typed-keys.md b/docs/reference/troubleshooting/serialize-without-typed-keys.md index dd4754c8a..5542e846a 100644 --- a/docs/reference/troubleshooting/serialize-without-typed-keys.md +++ b/docs/reference/troubleshooting/serialize-without-typed-keys.md @@ -1,5 +1,7 @@ --- navigation_title: Serializing without typed keys +mapped_pages: + - https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/serialize-without-typed-keys.html --- # Serializing aggregations and suggestions without typed keys