Skip to content

Commit d593b64

Browse files
committed
[FEATURE] Get rid of the dependency on OpenSearch core by introducing new OpenSearchTransport based on Apache HttpClient 5
Signed-off-by: Andriy Redko <[email protected]>
1 parent c171531 commit d593b64

39 files changed

+3636
-29
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
77
- Github workflow for dependabot PRs ([#247](https://github.com/opensearch-project/opensearch-java/pull/247))
88
- Add javadoc link for the client ([#255](https://github.com/opensearch-project/opensearch-java/pull/255))
99
- Add 1-click release workflows ([#321](https://github.com/opensearch-project/opensearch-java/pull/321))
10+
- Get rid of the dependency on OpenSearch core by introducing new OpenSearchTransport based on Apache HttpClient 5 ([#281](https://github.com/opensearch-project/opensearch-java/pull/281))
11+
1012
### Dependencies
1113
- Bumps `classgraph` from 4.8.149 to 4.8.154
1214

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.client.transport;
10+
11+
public final class TransportHeaders {
12+
public static final String ACCEPT = "Accept";
13+
public static final String USER_AGENT = "User-Agent";
14+
15+
private TransportHeaders() {
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.client.transport.httpclient5;
10+
11+
import java.util.AbstractMap;
12+
import java.util.ArrayList;
13+
import java.util.Collection;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.Locale;
17+
import java.util.Map;
18+
import java.util.Objects;
19+
import java.util.Map.Entry;
20+
import java.util.function.Function;
21+
import java.util.stream.Collectors;
22+
23+
import org.apache.hc.client5.http.config.RequestConfig;
24+
import org.apache.hc.core5.http.Header;
25+
import org.apache.hc.core5.http.message.BasicHeader;
26+
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
27+
import org.opensearch.client.transport.TransportOptions;
28+
import org.opensearch.client.transport.Version;
29+
30+
import static org.opensearch.client.transport.TransportHeaders.ACCEPT;
31+
import static org.opensearch.client.transport.TransportHeaders.USER_AGENT;
32+
33+
public class ApacheHttpClient5Options implements TransportOptions {
34+
/**
35+
* Default request options.
36+
*/
37+
public static final ApacheHttpClient5Options DEFAULT = new Builder(
38+
Collections.emptyList(),
39+
HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory.DEFAULT,
40+
null,
41+
null
42+
).build();
43+
44+
private final List<Header> headers;
45+
private final HttpAsyncResponseConsumerFactory httpAsyncResponseConsumerFactory;
46+
private final WarningsHandler warningsHandler;
47+
private final RequestConfig requestConfig;
48+
49+
private ApacheHttpClient5Options(Builder builder) {
50+
this.headers = Collections.unmodifiableList(new ArrayList<>(builder.headers));
51+
this.httpAsyncResponseConsumerFactory = builder.httpAsyncResponseConsumerFactory;
52+
this.warningsHandler = builder.warningsHandler;
53+
this.requestConfig = builder.requestConfig;
54+
}
55+
56+
public HttpAsyncResponseConsumerFactory getHttpAsyncResponseConsumerFactory() {
57+
return httpAsyncResponseConsumerFactory;
58+
}
59+
60+
public WarningsHandler getWarningsHandler() {
61+
return warningsHandler;
62+
}
63+
64+
public RequestConfig getRequestConfig() {
65+
return requestConfig;
66+
}
67+
68+
@Override
69+
public Collection<Entry<String, String>> headers() {
70+
return headers.stream()
71+
.map(h -> new AbstractMap.SimpleImmutableEntry<>(h.getName(), h.getValue()))
72+
.collect(Collectors.toList());
73+
}
74+
75+
@Override
76+
public Map<String, String> queryParameters() {
77+
return null;
78+
}
79+
80+
@Override
81+
public Function<List<String>, Boolean> onWarnings() {
82+
if (warningsHandler == null) {
83+
return null;
84+
} else {
85+
return warnings -> warningsHandler.warningsShouldFailRequest(warnings);
86+
}
87+
}
88+
89+
@Override
90+
public Builder toBuilder() {
91+
return new Builder(headers, httpAsyncResponseConsumerFactory, warningsHandler, requestConfig);
92+
}
93+
94+
public static class Builder implements TransportOptions.Builder {
95+
private final List<Header> headers;
96+
private HttpAsyncResponseConsumerFactory httpAsyncResponseConsumerFactory;
97+
private WarningsHandler warningsHandler;
98+
private RequestConfig requestConfig;
99+
100+
private Builder(Builder builder) {
101+
this(builder.headers, builder.httpAsyncResponseConsumerFactory,
102+
builder.warningsHandler, builder.requestConfig);
103+
}
104+
105+
private Builder(
106+
List<Header> headers,
107+
HttpAsyncResponseConsumerFactory httpAsyncResponseConsumerFactory,
108+
WarningsHandler warningsHandler,
109+
RequestConfig requestConfig
110+
) {
111+
this.headers = new ArrayList<>(headers);
112+
this.httpAsyncResponseConsumerFactory = httpAsyncResponseConsumerFactory;
113+
this.warningsHandler = warningsHandler;
114+
this.requestConfig = requestConfig;
115+
}
116+
117+
/**
118+
* Add the provided header to the request.
119+
*
120+
* @param name the header name
121+
* @param value the header value
122+
* @throws NullPointerException if {@code name} or {@code value} is null.
123+
*/
124+
@Override
125+
public Builder addHeader(String name, String value) {
126+
Objects.requireNonNull(name, "header name cannot be null");
127+
Objects.requireNonNull(value, "header value cannot be null");
128+
this.headers.add(new ReqHeader(name, value));
129+
return this;
130+
}
131+
132+
@Override
133+
public TransportOptions.Builder setParameter(String name, String value) {
134+
return this;
135+
}
136+
137+
/**
138+
* Called if there are warnings to determine if those warnings should fail the request.
139+
*/
140+
@Override
141+
public TransportOptions.Builder onWarnings(Function<List<String>, Boolean> listener) {
142+
if (listener == null) {
143+
setWarningsHandler(null);
144+
} else {
145+
setWarningsHandler(w -> {
146+
if (w != null && !w.isEmpty()) {
147+
return listener.apply(w);
148+
} else {
149+
return false;
150+
}
151+
});
152+
}
153+
154+
return this;
155+
}
156+
157+
/**
158+
* Set the {@link HttpAsyncResponseConsumerFactory} used to create one
159+
* {@link AsyncResponseConsumer} callback per retry. Controls how the
160+
* response body gets streamed from a non-blocking HTTP connection on the
161+
* client side.
162+
*
163+
* @param httpAsyncResponseConsumerFactory factory for creating {@link AsyncResponseConsumer}.
164+
* @throws NullPointerException if {@code httpAsyncResponseConsumerFactory} is null.
165+
*/
166+
public void setHttpAsyncResponseConsumerFactory(HttpAsyncResponseConsumerFactory httpAsyncResponseConsumerFactory) {
167+
this.httpAsyncResponseConsumerFactory = Objects.requireNonNull(
168+
httpAsyncResponseConsumerFactory,
169+
"httpAsyncResponseConsumerFactory cannot be null"
170+
);
171+
}
172+
173+
/**
174+
* How this request should handle warnings. If null (the default) then
175+
* this request will default to the behavior dictacted by
176+
* `setStrictDeprecationMode`.
177+
* <p>
178+
* This can be set to {@link WarningsHandler#PERMISSIVE} if the client
179+
* should ignore all warnings which is the same behavior as setting
180+
* strictDeprecationMode to true. It can be set to
181+
* {@link WarningsHandler#STRICT} if the client should fail if there are
182+
* any warnings which is the same behavior as settings
183+
* strictDeprecationMode to false.
184+
* <p>
185+
* It can also be set to a custom implementation of
186+
* {@linkplain WarningsHandler} to permit only certain warnings or to
187+
* fail the request if the warnings returned don't
188+
* <strong>exactly</strong> match some set.
189+
*
190+
* @param warningsHandler the {@link WarningsHandler} to be used
191+
*/
192+
public void setWarningsHandler(WarningsHandler warningsHandler) {
193+
this.warningsHandler = warningsHandler;
194+
}
195+
196+
/**
197+
* set RequestConfig, which can set socketTimeout, connectTimeout
198+
* and so on by request
199+
* @param requestConfig http client RequestConfig
200+
* @return Builder
201+
*/
202+
public Builder setRequestConfig(RequestConfig requestConfig) {
203+
this.requestConfig = requestConfig;
204+
return this;
205+
}
206+
207+
@Override
208+
public ApacheHttpClient5Options build() {
209+
return new ApacheHttpClient5Options(this);
210+
}
211+
}
212+
213+
static ApacheHttpClient5Options initialOptions() {
214+
String ua = String.format(
215+
Locale.ROOT,
216+
"opensearch-java/%s (Java/%s)",
217+
Version.VERSION == null ? "Unknown" : Version.VERSION.toString(),
218+
System.getProperty("java.version")
219+
);
220+
221+
return new ApacheHttpClient5Options(
222+
DEFAULT.toBuilder()
223+
.addHeader(USER_AGENT, ua)
224+
.addHeader(ACCEPT, ApacheHttpClient5Transport.JsonContentType.toString())
225+
);
226+
}
227+
228+
static ApacheHttpClient5Options of(TransportOptions options) {
229+
if (options instanceof ApacheHttpClient5Options) {
230+
return (ApacheHttpClient5Options)options;
231+
232+
} else {
233+
final Builder builder = new Builder(DEFAULT.toBuilder());
234+
options.headers().forEach(h -> builder.addHeader(h.getKey(), h.getValue()));
235+
options.queryParameters().forEach(builder::setParameter);
236+
builder.onWarnings(options.onWarnings());
237+
return builder.build();
238+
}
239+
}
240+
241+
/**
242+
* Custom implementation of {@link BasicHeader} that overrides equals and
243+
* hashCode so it is easier to test equality of {@link ApacheHttpClient5Options}.
244+
*/
245+
static final class ReqHeader extends BasicHeader {
246+
ReqHeader(String name, String value) {
247+
super(name, value);
248+
}
249+
250+
@Override
251+
public boolean equals(Object other) {
252+
if (this == other) {
253+
return true;
254+
}
255+
if (other instanceof ReqHeader) {
256+
Header otherHeader = (Header) other;
257+
return Objects.equals(getName(), otherHeader.getName()) && Objects.equals(getValue(), otherHeader.getValue());
258+
}
259+
return false;
260+
}
261+
262+
@Override
263+
public int hashCode() {
264+
return Objects.hash(getName(), getValue());
265+
}
266+
}
267+
}

0 commit comments

Comments
 (0)