From 8fe2dcf689949be5821c9e438299ec6173bc0478 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:17:49 +1300 Subject: [PATCH 1/3] Bump lycheeverse/lychee-action from 2.7.0 to 2.8.0 (#1901) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v2.7.0...v2.8.0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 2e319523a4..093a37759a 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v6 - name: lychee Link Checker id: lychee - uses: lycheeverse/lychee-action@v2.7.0 + uses: lycheeverse/lychee-action@v2.8.0 with: args: --accept=200,403,429 "**/*.html" "**/*.md" "**/*.txt" "**/*.json" fail: true From 0d7b24e3421ff54a57598bde98942a681a1b1cbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:18:38 +1300 Subject: [PATCH 2/3] Bump actions/upload-artifact from 6 to 7 (#1902) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-integration-unreleased.yml | 2 +- .github/workflows/test-integration.yml | 2 +- .github/workflows/test-unit.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-integration-unreleased.yml b/.github/workflows/test-integration-unreleased.yml index 9d98c6b9b2..9eba14e984 100644 --- a/.github/workflows/test-integration-unreleased.yml +++ b/.github/workflows/test-integration-unreleased.yml @@ -94,7 +94,7 @@ jobs: - name: Upload Reports if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: test-reports-os${{ matrix.entry.opensearch_ref }}-java${{ matrix.entry.java }} path: opensearch-java/java-client/build/reports/ diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index 420d53a6b9..a29e174887 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -64,7 +64,7 @@ jobs: - name: Upload Reports if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: test-reports-os${{ matrix.entry.opensearch_version }}-java${{ matrix.entry.java }} path: java-client/build/reports/ diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 54975022c0..071335068b 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -25,7 +25,7 @@ jobs: - name: Upload Reports if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: test-reports-java${{ matrix.java }}-${{ runner.os }} path: java-client/build/reports/ @@ -59,7 +59,7 @@ jobs: - name: Upload Reports if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: test-reports-java8-${{ runner.os }} path: java-client/build/reports/ From 16b7747a47c6c04a49a8139e6be29bd2199b02dc Mon Sep 17 00:00:00 2001 From: Baekgyu Kim Date: Wed, 4 Mar 2026 06:44:43 +0900 Subject: [PATCH 3/3] [1887] Fix NPE in transport option conversion (#1888) Signed-off-by: Baekgyu --- CHANGELOG.md | 1 + .../httpclient5/ApacheHttpClient5Options.java | 21 ++-- .../rest_client/RestClientOptions.java | 24 +++-- .../ApacheHttpClient5OptionsTest.java | 100 ++++++++++++++++++ .../rest_client/RestClientOptionsTest.java | 100 ++++++++++++++++++ 5 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 java-client/src/test/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5OptionsTest.java create mode 100644 java-client/src/test/java/org/opensearch/client/transport/rest_client/RestClientOptionsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c21c13f286..4f0d35d2a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Removed ### Fixed +- Fix NPEs while converting transport options when null options, headers, or query parameters are provided ([#1887](https://github.com/opensearch-project/opensearch-java/pull/1888)) ### Security diff --git a/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Options.java b/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Options.java index fae5c41998..d2a9f0bc2d 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Options.java +++ b/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Options.java @@ -220,16 +220,25 @@ static ApacheHttpClient5Options initialOptions() { } static ApacheHttpClient5Options of(TransportOptions options) { + if (options == null) return DEFAULT; + if (options instanceof ApacheHttpClient5Options) { return (ApacheHttpClient5Options) options; + } - } else { - final Builder builder = new Builder(DEFAULT.toBuilder()); - options.headers().forEach(h -> builder.addHeader(h.getKey(), h.getValue())); - options.queryParameters().forEach(builder::setParameter); - builder.onWarnings(options.onWarnings()); - return builder.build(); + final Builder builder = new Builder(DEFAULT.toBuilder()); + final Collection> headers = options.headers(); + if (headers != null && !headers.isEmpty()) { + headers.forEach(h -> builder.addHeader(h.getKey(), h.getValue())); } + + final Map parameters = options.queryParameters(); + if (parameters != null && !parameters.isEmpty()) { + parameters.forEach(builder::setParameter); + } + + builder.onWarnings(options.onWarnings()); + return builder.build(); } /** diff --git a/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientOptions.java b/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientOptions.java index 830a04ae92..7d2bb96ad4 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientOptions.java +++ b/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientOptions.java @@ -57,16 +57,28 @@ public class RestClientOptions implements TransportOptions { private final RequestOptions options; static RestClientOptions of(TransportOptions options) { + if (options == null) { + return new Builder(RequestOptions.DEFAULT.toBuilder()).build(); + } + if (options instanceof RestClientOptions) { return (RestClientOptions) options; + } + + final Builder builder = new Builder(RequestOptions.DEFAULT.toBuilder()); - } else { - final Builder builder = new Builder(RequestOptions.DEFAULT.toBuilder()); - options.headers().forEach(h -> builder.addHeader(h.getKey(), h.getValue())); - options.queryParameters().forEach(builder::setParameter); - builder.onWarnings(options.onWarnings()); - return builder.build(); + final Collection> headers = options.headers(); + if (headers != null && !headers.isEmpty()) { + headers.forEach(h -> builder.addHeader(h.getKey(), h.getValue())); } + + final Map parameters = options.queryParameters(); + if (parameters != null && !parameters.isEmpty()) { + parameters.forEach(builder::setParameter); + } + + builder.onWarnings(options.onWarnings()); + return builder.build(); } public RestClientOptions(RequestOptions options) { diff --git a/java-client/src/test/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5OptionsTest.java b/java-client/src/test/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5OptionsTest.java new file mode 100644 index 0000000000..d51f074019 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5OptionsTest.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.transport.httpclient5; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import org.junit.Assert; +import org.junit.Test; +import org.opensearch.client.transport.TransportOptions; + +public class ApacheHttpClient5OptionsTest extends Assert { + @Test + public void testOfWithNullOptions() { + // Null input should fall back to defaults instead of throwing. + ApacheHttpClient5Options options = ApacheHttpClient5Options.of(null); + + assertNotNull(options); + assertNotNull(options.headers()); + } + + @Test + public void testOfWithNullHeaders() { + // Missing headers from source options should be treated as empty. + ApacheHttpClient5Options options = ApacheHttpClient5Options.of( + transportOptions(null, Collections.emptyMap(), warnings -> warnings != null && !warnings.isEmpty()) + ); + + assertNotNull(options); + assertNotNull(options.headers()); + assertTrue(options.headers().isEmpty()); + assertNotNull(options.onWarnings()); + assertTrue(options.onWarnings().apply(Collections.singletonList("warning"))); + } + + @Test + public void testOfWithNullQueryParameters() { + // Null query parameters should not drop other fields during conversion. + ApacheHttpClient5Options options = ApacheHttpClient5Options.of( + transportOptions( + Collections.singletonList(new AbstractMap.SimpleImmutableEntry<>("x-test-header", "value")), + null, + warnings -> warnings != null && !warnings.isEmpty() + ) + ); + + assertNotNull(options); + assertNotNull(options.onWarnings()); + assertTrue(options.onWarnings().apply(Collections.singletonList("warning"))); + assertTrue(containsHeader(options.headers(), "x-test-header", "value")); + } + + private static boolean containsHeader(Collection> headers, String name, String value) { + for (Entry header : headers) { + if (name.equals(header.getKey()) && value.equals(header.getValue())) { + return true; + } + } + + return false; + } + + private static TransportOptions transportOptions( + Collection> headers, + Map queryParameters, + Function, Boolean> warningsHandler + ) { + return new TransportOptions() { + @Override + public Collection> headers() { + return headers; + } + + @Override + public Map queryParameters() { + return queryParameters; + } + + @Override + public Function, Boolean> onWarnings() { + return warningsHandler; + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + }; + } +} diff --git a/java-client/src/test/java/org/opensearch/client/transport/rest_client/RestClientOptionsTest.java b/java-client/src/test/java/org/opensearch/client/transport/rest_client/RestClientOptionsTest.java new file mode 100644 index 0000000000..1d27b44924 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/transport/rest_client/RestClientOptionsTest.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.transport.rest_client; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import org.junit.Assert; +import org.junit.Test; +import org.opensearch.client.transport.TransportOptions; + +public class RestClientOptionsTest extends Assert { + @Test + public void testOfWithNullOptions() { + // Null input should fall back to defaults instead of throwing. + RestClientOptions options = RestClientOptions.of(null); + + assertNotNull(options); + assertNotNull(options.headers()); + } + + @Test + public void testOfWithNullHeaders() { + // Missing headers from source options should be treated as empty. + RestClientOptions options = RestClientOptions.of( + transportOptions(null, Collections.emptyMap(), warnings -> warnings != null && !warnings.isEmpty()) + ); + + assertNotNull(options); + assertNotNull(options.headers()); + assertTrue(options.headers().isEmpty()); + assertNotNull(options.onWarnings()); + assertTrue(options.onWarnings().apply(Collections.singletonList("warning"))); + } + + @Test + public void testOfWithNullQueryParameters() { + // Null query parameters should not drop other fields during conversion. + RestClientOptions options = RestClientOptions.of( + transportOptions( + Collections.singletonList(new AbstractMap.SimpleImmutableEntry<>("x-test-header", "value")), + null, + warnings -> warnings != null && !warnings.isEmpty() + ) + ); + + assertNotNull(options); + assertNotNull(options.onWarnings()); + assertTrue(options.onWarnings().apply(Collections.singletonList("warning"))); + assertTrue(containsHeader(options.headers(), "x-test-header", "value")); + } + + private static boolean containsHeader(Collection> headers, String name, String value) { + for (Entry header : headers) { + if (name.equals(header.getKey()) && value.equals(header.getValue())) { + return true; + } + } + + return false; + } + + private static TransportOptions transportOptions( + Collection> headers, + Map queryParameters, + Function, Boolean> warningsHandler + ) { + return new TransportOptions() { + @Override + public Collection> headers() { + return headers; + } + + @Override + public Map queryParameters() { + return queryParameters; + } + + @Override + public Function, Boolean> onWarnings() { + return warningsHandler; + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + }; + } +}