From 5763428df5f3220dfb4f4fc00cd504c1b3f2c639 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 24 May 2024 22:54:53 +0900 Subject: [PATCH 1/9] modify to not use URLEncodedUtils Signed-off-by: AntCode97 --- .../transport/endpoints/SimpleEndpoint.java | 5 +- .../opensearch/client/util/PathEncoder.java | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 java-client/src/main/java/org/opensearch/client/util/PathEncoder.java diff --git a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java index 04ce725a00..6409c845bc 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java +++ b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java @@ -35,10 +35,10 @@ import java.util.Collections; import java.util.Map; import java.util.function.Function; -import org.apache.hc.core5.net.URLEncodedUtils; import org.opensearch.client.json.JsonpDeserializer; import org.opensearch.client.opensearch._types.ErrorResponse; import org.opensearch.client.transport.JsonEndpoint; +import org.opensearch.client.util.PathEncoder; public class SimpleEndpoint implements JsonEndpoint { @@ -133,7 +133,6 @@ public static RuntimeException noPathTemplateFound(String what) { } public static void pathEncode(String src, StringBuilder dest) { - // TODO: avoid dependency on HttpClient here (and use something more efficient) - dest.append(URLEncodedUtils.formatSegments(src).substring(1)); + dest.append(PathEncoder.encode(src)); } } diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java new file mode 100644 index 0000000000..5feea870fb --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java @@ -0,0 +1,89 @@ +package org.opensearch.client.util; + +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; + +public class PathEncoder { + private static final BitSet GEN_DELIMITERS = new BitSet(256); + private static final BitSet SUB_DELIMITERS = new BitSet(256); + private static final BitSet UNRESERVED_CHARACTERS = new BitSet(256); + private static final BitSet URIC_CHARACTERS = new BitSet(256); + + // Initialize the character sets used for encoding + static { + initializeBitSets(); + } + + /** + * Initializes the character sets for URL encoding. + */ + private static void initializeBitSets() { + int[] genDelims = { 58, 47, 63, 35, 91, 93, 64 }; + int[] subDelims = { 33, 36, 38, 39, 40, 41, 42, 43, 44, 59, 61 }; + int[] unreservedChars = { 45, 46, 95, 126 }; + + for (int delim : genDelims) { + GEN_DELIMITERS.set(delim); + } + + for (int delim : subDelims) { + SUB_DELIMITERS.set(delim); + } + + UNRESERVED_CHARACTERS.set(97, 123); // a-z + UNRESERVED_CHARACTERS.set(65, 91); // A-Z + UNRESERVED_CHARACTERS.set(48, 58); // 0-9 + + for (int ch : unreservedChars) { + UNRESERVED_CHARACTERS.set(ch); + } + + URIC_CHARACTERS.or(SUB_DELIMITERS); + URIC_CHARACTERS.or(UNRESERVED_CHARACTERS); + } + + /** + * Encodes the given string as a URL path. + * + * @param src the string to encode + * @return the encoded URL path + */ + public static String encode(String src) { + StringBuilder buf = new StringBuilder(); + encodePathSegment(buf, src); + return buf.toString(); + } + + /** + * Encodes a single segment of the path. + * + * @param buf the StringBuilder to append the encoded segment to + * @param segment the path segment to encode + */ + private static void encodePathSegment(StringBuilder buf, String segment) { + if (segment != null) { + byte[] bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(segment)).array(); + encodeBytes(buf, bytes); + } + } + + /** + * Encodes the given byte array and appends the result to the StringBuilder. + * + * @param buf the StringBuilder to append the encoded bytes to + * @param bytes the byte array to encode + */ + private static void encodeBytes(StringBuilder buf, byte[] bytes) { + for (byte b : bytes) { + int c = b & 0xFF; + if (UNRESERVED_CHARACTERS.get(c)) { + buf.append((char) c); + } else { + buf.append('%'); + buf.append(Character.toUpperCase(Character.forDigit(c >> 4 & 0xF, 16))); + buf.append(Character.toUpperCase(Character.forDigit(c & 0xF, 16))); + } + } + } +} From f2476e2ae17844ced443eb4f162e17f772489b9d Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 24 May 2024 23:18:14 +0900 Subject: [PATCH 2/9] add a license header Signed-off-by: AntCode97 --- .../main/java/org/opensearch/client/util/PathEncoder.java | 8 ++++++++ .../java/org/opensearch/client/util/PathEncoderTest.java | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java index 5feea870fb..3af567a1aa 100644 --- a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java +++ b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java @@ -1,3 +1,11 @@ +/* + * 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.util; import java.nio.CharBuffer; diff --git a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java new file mode 100644 index 0000000000..a7e5a89dbb --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java @@ -0,0 +1,8 @@ +package org.opensearch.client.util; + +import junit.framework.TestCase; + +public class PathEncoderTest extends TestCase { + + public void testEncode() {} +} From 5ed94fbaa678c34512277e553f89b34513705e11 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 24 May 2024 23:42:00 +0900 Subject: [PATCH 3/9] add pathEncoder test Signed-off-by: AntCode97 --- .../client/util/PathEncoderTest.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java index a7e5a89dbb..d6e90d3a2c 100644 --- a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java +++ b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java @@ -1,8 +1,31 @@ package org.opensearch.client.util; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; -public class PathEncoderTest extends TestCase { +import org.junit.Test; - public void testEncode() {} +public class PathEncoderTest { + + @Test + public void testEncode() { + // Test with a simple string + String simpleString = "test"; + String encodedSimpleString = PathEncoder.encode(simpleString); + assertEquals(simpleString, encodedSimpleString); + + // Test with a string that contains special characters + String specialString = "a/b"; + String encodedSpecialString = PathEncoder.encode(specialString); + assertEquals("a%2Fb", encodedSpecialString); + + // Test with a string that contains alphanumeric characters + String alphanumericString = "abc123"; + String encodedAlphanumericString = PathEncoder.encode(alphanumericString); + assertEquals("abc123", encodedAlphanumericString); + + // Test with a string that contains multiple segments + String multiSegmentString = "a/b/c/_refresh"; + String encodedMultiSegmentString = PathEncoder.encode(multiSegmentString); + assertEquals("a%2Fb%2Fc%2F_refresh", encodedMultiSegmentString); + } } From 4d02494e9e8b121f10bea50b9ab4cc0581559eb2 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 24 May 2024 23:43:00 +0900 Subject: [PATCH 4/9] modify to use ByteBuffer in encodeBytes method Signed-off-by: AntCode97 --- .../java/org/opensearch/client/util/PathEncoder.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java index 3af567a1aa..8185c98e30 100644 --- a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java +++ b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java @@ -8,6 +8,7 @@ package org.opensearch.client.util; +import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.BitSet; @@ -71,7 +72,7 @@ public static String encode(String src) { */ private static void encodePathSegment(StringBuilder buf, String segment) { if (segment != null) { - byte[] bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(segment)).array(); + final ByteBuffer bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(segment)); encodeBytes(buf, bytes); } } @@ -82,9 +83,9 @@ private static void encodePathSegment(StringBuilder buf, String segment) { * @param buf the StringBuilder to append the encoded bytes to * @param bytes the byte array to encode */ - private static void encodeBytes(StringBuilder buf, byte[] bytes) { - for (byte b : bytes) { - int c = b & 0xFF; + private static void encodeBytes(StringBuilder buf, ByteBuffer bytes) { + while (bytes.hasRemaining()) { + int c = bytes.get() & 0xFF; if (UNRESERVED_CHARACTERS.get(c)) { buf.append((char) c); } else { From b10a16562b50d8ddf7c1a5a48b1ff81d552f705e Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Tue, 28 May 2024 18:53:21 +0900 Subject: [PATCH 5/9] modify to use URLEncoder Signed-off-by: AntCode97 --- .../client/transport/endpoints/SimpleEndpoint.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java index 6409c845bc..e7bc433b80 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java +++ b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java @@ -32,13 +32,14 @@ package org.opensearch.client.transport.endpoints; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; import java.util.function.Function; import org.opensearch.client.json.JsonpDeserializer; import org.opensearch.client.opensearch._types.ErrorResponse; import org.opensearch.client.transport.JsonEndpoint; -import org.opensearch.client.util.PathEncoder; public class SimpleEndpoint implements JsonEndpoint { @@ -133,6 +134,6 @@ public static RuntimeException noPathTemplateFound(String what) { } public static void pathEncode(String src, StringBuilder dest) { - dest.append(PathEncoder.encode(src)); + dest.append(URLEncoder.encode(src, StandardCharsets.UTF_8)); } } From 19abfef4492bc62dc0694b75833eaf95c053069a Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Tue, 28 May 2024 18:53:35 +0900 Subject: [PATCH 6/9] delete PathEncoder Signed-off-by: AntCode97 --- .../opensearch/client/util/PathEncoder.java | 98 ------------------- .../client/util/PathEncoderTest.java | 31 ------ 2 files changed, 129 deletions(-) delete mode 100644 java-client/src/main/java/org/opensearch/client/util/PathEncoder.java delete mode 100644 java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java deleted file mode 100644 index 8185c98e30..0000000000 --- a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.util; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; -import java.util.BitSet; - -public class PathEncoder { - private static final BitSet GEN_DELIMITERS = new BitSet(256); - private static final BitSet SUB_DELIMITERS = new BitSet(256); - private static final BitSet UNRESERVED_CHARACTERS = new BitSet(256); - private static final BitSet URIC_CHARACTERS = new BitSet(256); - - // Initialize the character sets used for encoding - static { - initializeBitSets(); - } - - /** - * Initializes the character sets for URL encoding. - */ - private static void initializeBitSets() { - int[] genDelims = { 58, 47, 63, 35, 91, 93, 64 }; - int[] subDelims = { 33, 36, 38, 39, 40, 41, 42, 43, 44, 59, 61 }; - int[] unreservedChars = { 45, 46, 95, 126 }; - - for (int delim : genDelims) { - GEN_DELIMITERS.set(delim); - } - - for (int delim : subDelims) { - SUB_DELIMITERS.set(delim); - } - - UNRESERVED_CHARACTERS.set(97, 123); // a-z - UNRESERVED_CHARACTERS.set(65, 91); // A-Z - UNRESERVED_CHARACTERS.set(48, 58); // 0-9 - - for (int ch : unreservedChars) { - UNRESERVED_CHARACTERS.set(ch); - } - - URIC_CHARACTERS.or(SUB_DELIMITERS); - URIC_CHARACTERS.or(UNRESERVED_CHARACTERS); - } - - /** - * Encodes the given string as a URL path. - * - * @param src the string to encode - * @return the encoded URL path - */ - public static String encode(String src) { - StringBuilder buf = new StringBuilder(); - encodePathSegment(buf, src); - return buf.toString(); - } - - /** - * Encodes a single segment of the path. - * - * @param buf the StringBuilder to append the encoded segment to - * @param segment the path segment to encode - */ - private static void encodePathSegment(StringBuilder buf, String segment) { - if (segment != null) { - final ByteBuffer bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(segment)); - encodeBytes(buf, bytes); - } - } - - /** - * Encodes the given byte array and appends the result to the StringBuilder. - * - * @param buf the StringBuilder to append the encoded bytes to - * @param bytes the byte array to encode - */ - private static void encodeBytes(StringBuilder buf, ByteBuffer bytes) { - while (bytes.hasRemaining()) { - int c = bytes.get() & 0xFF; - if (UNRESERVED_CHARACTERS.get(c)) { - buf.append((char) c); - } else { - buf.append('%'); - buf.append(Character.toUpperCase(Character.forDigit(c >> 4 & 0xF, 16))); - buf.append(Character.toUpperCase(Character.forDigit(c & 0xF, 16))); - } - } - } -} diff --git a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java deleted file mode 100644 index d6e90d3a2c..0000000000 --- a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.opensearch.client.util; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class PathEncoderTest { - - @Test - public void testEncode() { - // Test with a simple string - String simpleString = "test"; - String encodedSimpleString = PathEncoder.encode(simpleString); - assertEquals(simpleString, encodedSimpleString); - - // Test with a string that contains special characters - String specialString = "a/b"; - String encodedSpecialString = PathEncoder.encode(specialString); - assertEquals("a%2Fb", encodedSpecialString); - - // Test with a string that contains alphanumeric characters - String alphanumericString = "abc123"; - String encodedAlphanumericString = PathEncoder.encode(alphanumericString); - assertEquals("abc123", encodedAlphanumericString); - - // Test with a string that contains multiple segments - String multiSegmentString = "a/b/c/_refresh"; - String encodedMultiSegmentString = PathEncoder.encode(multiSegmentString); - assertEquals("a%2Fb%2Fc%2F_refresh", encodedMultiSegmentString); - } -} From c8052c858f83d99760d8bc9d89ccdc3afc4aedc6 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Wed, 29 May 2024 14:47:10 +0900 Subject: [PATCH 7/9] modify to use URLEncodedUtils per version per Classpath Signed-off-by: AntCode97 --- .../transport/endpoints/SimpleEndpoint.java | 5 +- .../opensearch/client/util/PathEncoder.java | 50 +++++++++++++++++++ .../client/opensearch/model/EndpointTest.java | 28 +++++++++-- .../client/util/PathEncoderTest.java | 31 ++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 java-client/src/main/java/org/opensearch/client/util/PathEncoder.java create mode 100644 java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java diff --git a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java index e7bc433b80..6409c845bc 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java +++ b/java-client/src/main/java/org/opensearch/client/transport/endpoints/SimpleEndpoint.java @@ -32,14 +32,13 @@ package org.opensearch.client.transport.endpoints; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; import java.util.function.Function; import org.opensearch.client.json.JsonpDeserializer; import org.opensearch.client.opensearch._types.ErrorResponse; import org.opensearch.client.transport.JsonEndpoint; +import org.opensearch.client.util.PathEncoder; public class SimpleEndpoint implements JsonEndpoint { @@ -134,6 +133,6 @@ public static RuntimeException noPathTemplateFound(String what) { } public static void pathEncode(String src, StringBuilder dest) { - dest.append(URLEncoder.encode(src, StandardCharsets.UTF_8)); + dest.append(PathEncoder.encode(src)); } } diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java new file mode 100644 index 0000000000..d64e83ca1f --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java @@ -0,0 +1,50 @@ +package org.opensearch.client.util; + +/* + * 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. + */ + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +public class PathEncoder { + + private final static String httpClient4UtilClassName = "org.apache.http.client.utils.URLEncodedUtils"; + private final static String httpClient5UtilClassName = "org.apache.hc.core5.net.URLEncodedUtils"; + private static final boolean isHttpClient5UtilPresent = isPresent(httpClient5UtilClassName); + + public static String encode(String uri) { + if (isHttpClient5UtilPresent) { + return encodeByUtilClass(uri, httpClient5UtilClassName); + } else { + return encodeByUtilClass(uri, httpClient4UtilClassName); + } + } + + private static String encodeByUtilClass(String uri, String className) { + try { + Method formatSegments = Class.forName(className).getMethod("formatSegments", Iterable.class, Charset.class); + String invoke = (String) formatSegments.invoke(null, Collections.singletonList(uri), StandardCharsets.UTF_8); + return invoke.substring(1); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to encode URI using " + className, e); + } + } + + private static boolean isPresent(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/model/EndpointTest.java b/java-client/src/test/java/org/opensearch/client/opensearch/model/EndpointTest.java index e434e12b85..547e608732 100644 --- a/java-client/src/test/java/org/opensearch/client/opensearch/model/EndpointTest.java +++ b/java-client/src/test/java/org/opensearch/client/opensearch/model/EndpointTest.java @@ -51,10 +51,19 @@ public void testArrayPathParameter() { assertEquals("/a/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); req = RefreshRequest.of(b -> b.index("a", "b")); - assertEquals("/a%2Cb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + if (isHttpClient5Present()) { + assertEquals("/a%2Cb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + + } else { + assertEquals("/a,b/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + } req = RefreshRequest.of(b -> b.index("a", "b", "c")); - assertEquals("/a%2Cb%2Cc/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + if (isHttpClient5Present()) { + assertEquals("/a%2Cb%2Cc/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + } else { + assertEquals("/a,b,c/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + } } @Test @@ -65,7 +74,11 @@ public void testPathEncoding() { assertEquals("/a%2Fb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); req = RefreshRequest.of(b -> b.index("a/b", "c/d")); - assertEquals("/a%2Fb%2Cc%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + if (isHttpClient5Present()) { + assertEquals("/a%2Fb%2Cc%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + } else { + assertEquals("/a%2Fb,c%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req)); + } } @@ -84,4 +97,13 @@ public void testArrayQueryParameter() { req = RefreshRequest.of(b -> b.expandWildcards(ExpandWildcard.All, ExpandWildcard.Closed)); assertEquals("all,closed", RefreshRequest._ENDPOINT.queryParameters(req).get("expand_wildcards")); } + + private static boolean isHttpClient5Present() { + try { + Class.forName("org.apache.hc.core5.net.URLEncodedUtils"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } } diff --git a/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java new file mode 100644 index 0000000000..d6e90d3a2c --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/util/PathEncoderTest.java @@ -0,0 +1,31 @@ +package org.opensearch.client.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class PathEncoderTest { + + @Test + public void testEncode() { + // Test with a simple string + String simpleString = "test"; + String encodedSimpleString = PathEncoder.encode(simpleString); + assertEquals(simpleString, encodedSimpleString); + + // Test with a string that contains special characters + String specialString = "a/b"; + String encodedSpecialString = PathEncoder.encode(specialString); + assertEquals("a%2Fb", encodedSpecialString); + + // Test with a string that contains alphanumeric characters + String alphanumericString = "abc123"; + String encodedAlphanumericString = PathEncoder.encode(alphanumericString); + assertEquals("abc123", encodedAlphanumericString); + + // Test with a string that contains multiple segments + String multiSegmentString = "a/b/c/_refresh"; + String encodedMultiSegmentString = PathEncoder.encode(multiSegmentString); + assertEquals("a%2Fb%2Fc%2F_refresh", encodedMultiSegmentString); + } +} From cfe77ae6f8eac730febf10ef729f95cb955e38c0 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 31 May 2024 13:05:00 +0900 Subject: [PATCH 8/9] fix to use MethodHandle instead of reflection Signed-off-by: AntCode97 --- .../opensearch/client/util/PathEncoder.java | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java index d64e83ca1f..171353221a 100644 --- a/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java +++ b/java-client/src/main/java/org/opensearch/client/util/PathEncoder.java @@ -8,43 +8,51 @@ * compatible open source license. */ -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; public class PathEncoder { + private final static String HTTP_CLIENT4_UTILS_CLASS = "org.apache.http.client.utils.URLEncodedUtils"; + private final static String HTTP_CLIENT5_UTILS_CLASS = "org.apache.hc.core5.net.URLEncodedUtils"; + private final static MethodHandle FORMAT_SEGMENTS_MH; - private final static String httpClient4UtilClassName = "org.apache.http.client.utils.URLEncodedUtils"; - private final static String httpClient5UtilClassName = "org.apache.hc.core5.net.URLEncodedUtils"; - private static final boolean isHttpClient5UtilPresent = isPresent(httpClient5UtilClassName); + static { + Class clazz = null; + try { + // Try Apache HttpClient5 first since this is a default one + clazz = Class.forName(HTTP_CLIENT5_UTILS_CLASS); + } catch (final ClassNotFoundException ex) { + try { + // Fallback to Apache HttpClient4 + clazz = Class.forName(HTTP_CLIENT4_UTILS_CLASS); + } catch (final ClassNotFoundException ex1) { + clazz = null; + } + } - public static String encode(String uri) { - if (isHttpClient5UtilPresent) { - return encodeByUtilClass(uri, httpClient5UtilClassName); - } else { - return encodeByUtilClass(uri, httpClient4UtilClassName); + if (clazz == null) { + throw new IllegalStateException( + "Either '" + HTTP_CLIENT5_UTILS_CLASS + "' or '" + HTTP_CLIENT4_UTILS_CLASS + "' is required by not found on classpath" + ); } - } - private static String encodeByUtilClass(String uri, String className) { try { - Method formatSegments = Class.forName(className).getMethod("formatSegments", Iterable.class, Charset.class); - String invoke = (String) formatSegments.invoke(null, Collections.singletonList(uri), StandardCharsets.UTF_8); - return invoke.substring(1); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Failed to encode URI using " + className, e); + FORMAT_SEGMENTS_MH = MethodHandles.lookup() + .findStatic(clazz, "formatSegments", MethodType.methodType(String.class, Iterable.class, Charset.class)); + } catch (final NoSuchMethodException | IllegalAccessException ex) { + throw new IllegalStateException("Unable to find 'formatSegments' method in " + clazz + " class"); } } - private static boolean isPresent(String className) { + public static String encode(String uri) { try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; + return ((String) FORMAT_SEGMENTS_MH.invoke(Collections.singletonList(uri), StandardCharsets.UTF_8)).substring(1); + } catch (final Throwable ex) { + throw new RuntimeException("Unable to encode URI: " + uri, ex); } } - } From cdf784085cccb825b3554302915ea344f8802893 Mon Sep 17 00:00:00 2001 From: AntCode97 Date: Fri, 31 May 2024 23:59:56 +0900 Subject: [PATCH 9/9] add CHANGELOG.md Signed-off-by: AntCode97 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 761c7f9b3b..b487551405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ This section is for maintaining a changelog for all breaking changes for the cli ### Fixed - ApacheHttpClient5Transport requires Apache Commons Logging dependency ([#1003](https://github.com/opensearch-project/opensearch-java/pull/1003)) - Preserve caller information in stack traces when synchronous callers use asynchronous transports ([#656](https://github.com/opensearch-project/opensearch-java/pull/656)) +- Fix java.lang.NoSuchMethodError: org.apache.http.client.utils.URLEncodedUtils.formatSegments w/o httpclient ([#999](https://github.com/opensearch-project/opensearch-java/pull/999)) ### Security