feat(common): replace jersey dependencies with OkHttp (Breaking Change) (#133)

* remove jersey

* Update ci.yml

* refact: replace params okhttp3.Headers to internal RestHeaders

* fix: licence dependency

* fix: test error

* fix: code format issue

* fix: unit test error


---------

Co-authored-by: imbajin <jin@apache.org>
This commit is contained in:
小宇 2023-11-22 09:37:02 +08:00 committed by GitHub
parent b49be05171
commit 5ad55fb82f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 885 additions and 910 deletions

View File

@ -1,6 +1,7 @@
name: "hugegraph-commons ci"
on:
workflow_dispatch:
push:
branches:
- master

View File

@ -1,6 +1,7 @@
name: "license checker"
on:
workflow_dispatch:
push:
branches:
- master

View File

@ -15,8 +15,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@ -32,11 +32,14 @@
<description>
hugegraph-common is a common module for HugeGraph and its peripheral components.
hugegraph-common encapsulates locks, configurations, events, iterators, rest and some
numeric or collection util classes to simplify the development of HugeGraph and its components.
numeric or collection util classes to simplify the development of HugeGraph and its
components.
</description>
<properties>
<!-- Use parent params -->
<lombok.version>1.18.8</lombok.version>
<okhttp.version>4.10.0</okhttp.version>
</properties>
<dependencies>
@ -196,76 +199,6 @@
<version>${jackson.version}</version>
</dependency>
<!-- jersey -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-base</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</exclusion>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<groupId>com.fasterxml.jackson.module</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.connectors</groupId>
<artifactId>jersey-apache-connector</artifactId>
<version>${jersey.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>commons-codec</groupId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.hk2.version}</version>
<exclusions>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>org.javassist</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jakarta.xml.version}</version>
<exclusions>
<exclusion>
<artifactId>jakarta.activation-api</artifactId>
<groupId>jakarta.activation</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
@ -278,8 +211,36 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-bom</artifactId>
<version>${okhttp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>

View File

@ -17,188 +17,219 @@
package org.apache.hugegraph.rest;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpHeaders;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.ExecutorUtil;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.JerseyClientBuilder;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.message.GZipEncoder;
import org.glassfish.jersey.uri.UriComponent;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.util.JsonUtil;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ImmutableMap;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation.Builder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Variant;
import lombok.SneakyThrows;
import okhttp3.ConnectionPool;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;
/**
* This class provides an abstract implementation of the RestClient interface.
* It provides methods for making HTTP requests (GET, POST, PUT, DELETE) to a REST API.
* Note: It uses the OkHttp library to make these requests for now.
*/
public abstract class AbstractRestClient implements RestClient {
// Time unit: hours
private static final long TTL = 24L;
// Time unit: ms
private static final long IDLE_TIME = 40L * 1000L;
private final ThreadLocal<String> authContext;
private static final String TOKEN_KEY = "tokenKey";
private final OkHttpClient client;
private final Client client;
private final WebTarget target;
private PoolingHttpClientConnectionManager pool;
private ScheduledExecutorService cleanExecutor;
private final String baseUrl;
public AbstractRestClient(String url, int timeout) {
this(url, new ConfigBuilder().configTimeout(timeout).build());
this(url, RestClientConfig.builder().timeout(timeout).build());
}
public AbstractRestClient(String url, String user, String password,
int timeout) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configUser(user, password)
.build());
}
public AbstractRestClient(String url, int timeout,
int maxTotal, int maxPerRoute) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configPool(maxTotal, maxPerRoute)
.build());
public AbstractRestClient(String url, String user, String password, int timeout) {
this(url, RestClientConfig.builder()
.user(user)
.password(password)
.timeout(timeout)
.build());
}
public AbstractRestClient(String url, int timeout, int idleTime,
int maxTotal, int maxPerRoute) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configIdleTime(idleTime)
.configPool(maxTotal, maxPerRoute)
.build());
int maxConns, int maxConnsPerRoute) {
this(url, RestClientConfig.builder()
.idleTime(idleTime)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.build());
}
public AbstractRestClient(String url, String user, String password,
int timeout, int maxTotal, int maxPerRoute) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configUser(user, password)
.configPool(maxTotal, maxPerRoute)
.build());
}
public AbstractRestClient(String url, String user, String password,
int timeout, int maxTotal, int maxPerRoute,
String trustStoreFile,
String trustStorePassword) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configUser(user, password)
.configPool(maxTotal, maxPerRoute)
.configSSL(trustStoreFile,
trustStorePassword)
.build());
}
public AbstractRestClient(String url, String token, int timeout) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configToken(token)
.build());
public AbstractRestClient(String url, String user, String password, int timeout,
int maxConns, int maxConnsPerRoute,
String trustStoreFile, String trustStorePassword) {
this(url, RestClientConfig.builder()
.user(user).password(password)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword)
.build());
}
public AbstractRestClient(String url, String token, int timeout,
int maxTotal, int maxPerRoute) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configToken(token)
.configPool(maxTotal, maxPerRoute)
.build());
int maxConns, int maxConnsPerRoute,
String trustStoreFile, String trustStorePassword) {
this(url, RestClientConfig.builder()
.token(token)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword)
.build());
}
public AbstractRestClient(String url, String token, int timeout,
int maxTotal, int maxPerRoute,
String trustStoreFile,
String trustStorePassword) {
this(url, new ConfigBuilder().configTimeout(timeout)
.configToken(token)
.configPool(maxTotal, maxPerRoute)
.configSSL(trustStoreFile,
trustStorePassword)
.build());
public AbstractRestClient(String url, RestClientConfig config) {
this.baseUrl = url;
this.client = buildOkHttpClient(config);
this.authContext = new InheritableThreadLocal<>();
}
public AbstractRestClient(String url, ClientConfig config) {
configConnectionManager(url, config);
this.client = JerseyClientBuilder.newClient(config);
this.client.register(GZipEncoder.class);
this.target = this.client.target(url);
this.pool = (PoolingHttpClientConnectionManager) config.getProperty(
ApacheClientProperties.CONNECTION_MANAGER);
if (this.pool != null) {
this.cleanExecutor = ExecutorUtil.newScheduledThreadPool(
"conn-clean-worker-%d");
Number idleTimeProp = (Number) config.getProperty("idleTime");
final long idleTime = idleTimeProp == null ?
IDLE_TIME : idleTimeProp.longValue();
final long checkPeriod = idleTime / 2L;
this.cleanExecutor.scheduleWithFixedDelay(() -> {
PoolStats stats = this.pool.getTotalStats();
int using = stats.getLeased() + stats.getPending();
if (using > 0) {
// Do clean only when all connections are idle
return;
}
// Release connections when all clients are inactive
this.pool.closeIdleConnections(idleTime, TimeUnit.MILLISECONDS);
this.pool.closeExpiredConnections();
}, checkPeriod, checkPeriod, TimeUnit.MILLISECONDS);
private static RequestBody buildRequestBody(Object body, RestHeaders headers) {
String contentType = parseContentType(headers);
String bodyContent;
if (RestHeaders.APPLICATION_JSON.equals(contentType)) {
if (body == null) {
bodyContent = "{}";
} else {
bodyContent = JsonUtil.toJson(body);
}
} else {
bodyContent = String.valueOf(body);
}
RequestBody requestBody = RequestBody.create(bodyContent.getBytes(),
MediaType.parse(contentType));
if (headers != null &&
"gzip".equals(headers.get(RestHeaders.CONTENT_ENCODING))) {
requestBody = gzipBody(requestBody);
}
return requestBody;
}
protected abstract void checkStatus(Response response,
Response.Status... statuses);
private static RequestBody gzipBody(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}
protected Response request(Callable<Response> method) {
try {
return method.call();
} catch (Exception e) {
throw new ClientException("Failed to do request", e);
@Override
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override
public void writeTo(@NotNull BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
private static String parseContentType(RestHeaders headers) {
if (headers != null) {
String contentType = headers.get(RestHeaders.CONTENT_TYPE);
if (contentType != null) {
return contentType;
}
}
return RestHeaders.APPLICATION_JSON;
}
private OkHttpClient buildOkHttpClient(RestClientConfig config) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (config.getTimeout() != null) {
builder.connectTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(config.getTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getMaxIdleConns() != null || config.getIdleTime() != null) {
ConnectionPool connectionPool = new ConnectionPool(config.getMaxIdleConns(),
config.getIdleTime(),
TimeUnit.SECONDS);
builder.connectionPool(connectionPool);
}
// auth header interceptor
if (StringUtils.isNotBlank(config.getUser()) &&
StringUtils.isNotBlank(config.getPassword())) {
builder.addInterceptor(new OkHttpBasicAuthInterceptor(config.getUser(),
config.getPassword()));
}
if (StringUtils.isNotBlank(config.getToken())) {
builder.addInterceptor(new OkHttpTokenInterceptor(config.getToken()));
}
// ssl
configSsl(builder, this.baseUrl, config.getTrustStoreFile(),
config.getTrustStorePassword());
OkHttpClient okHttpClient = builder.build();
if (config.getMaxConns() != null) {
okHttpClient.dispatcher().setMaxRequests(config.getMaxConns());
}
if (config.getMaxConnsPerRoute() != null) {
okHttpClient.dispatcher().setMaxRequestsPerHost(config.getMaxConnsPerRoute());
}
return okHttpClient;
}
@SneakyThrows
private void configSsl(OkHttpClient.Builder builder, String url, String trustStoreFile,
String trustStorePass) {
if (StringUtils.isBlank(trustStoreFile) || StringUtils.isBlank(trustStorePass)) {
return;
}
X509TrustManager trustManager = trustManagerForCertificates(trustStoreFile, trustStorePass);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new HostNameVerifier(url));
}
@Override
@ -207,31 +238,69 @@ public abstract class AbstractRestClient implements RestClient {
}
@Override
public RestResult post(String path, Object object,
MultivaluedMap<String, Object> headers) {
public RestResult post(String path, Object object, RestHeaders headers) {
return this.post(path, object, headers, null);
}
@Override
public RestResult post(String path, Object object,
Map<String, Object> params) {
public RestResult post(String path, Object object, Map<String, Object> params) {
return this.post(path, object, null, params);
}
private Request.Builder genRequestBuilder(String path, String id, RestHeaders headers,
Map<String, Object> params) {
HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(this.baseUrl))
.newBuilder()
.addPathSegments(path);
if (id != null) {
urlBuilder.addPathSegment(id);
}
if (params != null) {
params.forEach((name, value) -> {
if (value == null) {
return;
}
if (value instanceof Collection) {
for (Object i : (Collection<?>) value) {
urlBuilder.addQueryParameter(name, String.valueOf(i));
}
} else {
urlBuilder.addQueryParameter(name, String.valueOf(value));
}
});
}
Request.Builder builder = newRequestBuilder().url(urlBuilder.build());
if (headers != null) {
builder.headers(headers.toOkHttpHeader());
}
this.attachAuthToRequest(builder);
return builder;
}
/**
* In order to provide subclasses with overloading opportunities
*/
protected Request.Builder newRequestBuilder() {
return new Request.Builder();
}
@SneakyThrows
@Override
public RestResult post(String path, Object object,
MultivaluedMap<String, Object> headers,
public RestResult post(String path, Object object, RestHeaders headers,
Map<String, Object> params) {
Pair<Builder, Entity<?>> pair = this.buildRequest(path, null, object,
headers, params);
Response response = this.request(() -> {
// pair.getLeft() is builder, pair.getRight() is entity (http body)
return pair.getLeft().post(pair.getRight());
});
// If check status failed, throw client exception.
checkStatus(response, Response.Status.CREATED,
Response.Status.OK, Response.Status.ACCEPTED);
return new RestResult(response);
Request.Builder requestBuilder = genRequestBuilder(path, null, headers, params);
requestBuilder.post(buildRequestBody(object, headers));
try (Response response = request(requestBuilder)) {
checkStatus(response, 200, 201, 202);
return new RestResult(response);
}
}
@Override
@ -240,30 +309,27 @@ public abstract class AbstractRestClient implements RestClient {
}
@Override
public RestResult put(String path, String id, Object object,
MultivaluedMap<String, Object> headers) {
public RestResult put(String path, String id, Object object, RestHeaders headers) {
return this.put(path, id, object, headers, null);
}
@Override
public RestResult put(String path, String id, Object object,
Map<String, Object> params) {
public RestResult put(String path, String id, Object object, Map<String, Object> params) {
return this.put(path, id, object, null, params);
}
@SneakyThrows
@Override
public RestResult put(String path, String id, Object object,
MultivaluedMap<String, Object> headers,
RestHeaders headers,
Map<String, Object> params) {
Pair<Builder, Entity<?>> pair = this.buildRequest(path, id, object,
headers, params);
Response response = this.request(() -> {
// pair.getLeft() is builder, pair.getRight() is entity (http body)
return pair.getLeft().put(pair.getRight());
});
// If check status failed, throw client exception.
checkStatus(response, Response.Status.OK, Response.Status.ACCEPTED);
return new RestResult(response);
Request.Builder requestBuilder = genRequestBuilder(path, id, headers, params);
requestBuilder.put(buildRequestBody(object, headers));
try (Response response = request(requestBuilder)) {
checkStatus(response, 200, 202);
return new RestResult(response);
}
}
@Override
@ -281,29 +347,14 @@ public abstract class AbstractRestClient implements RestClient {
return this.get(path, id, ImmutableMap.of());
}
@SneakyThrows
private RestResult get(String path, String id, Map<String, Object> params) {
Ref<WebTarget> target = Refs.of(this.target);
for (String key : params.keySet()) {
Object value = params.get(key);
if (value instanceof Collection) {
for (Object i : (Collection<?>) value) {
target.set(target.get().queryParam(key, i));
}
} else {
target.set(target.get().queryParam(key, value));
}
Request.Builder requestBuilder = genRequestBuilder(path, id, null, params);
try (Response response = request(requestBuilder)) {
checkStatus(response, 200);
return new RestResult(response);
}
Response response = this.request(() -> {
WebTarget webTarget = target.get();
Builder builder = id == null ? webTarget.path(path).request() :
webTarget.path(path).path(encode(id)).request();
this.attachAuthToRequest(builder);
return builder.get();
});
checkStatus(response, Response.Status.OK);
return new RestResult(response);
}
@Override
@ -316,40 +367,35 @@ public abstract class AbstractRestClient implements RestClient {
return this.delete(path, id, ImmutableMap.of());
}
@SneakyThrows
private RestResult delete(String path, String id,
Map<String, Object> params) {
Ref<WebTarget> target = Refs.of(this.target);
for (String key : params.keySet()) {
target.set(target.get().queryParam(key, params.get(key)));
Request.Builder requestBuilder = genRequestBuilder(path, id, null, params);
requestBuilder.delete();
try (Response response = request(requestBuilder)) {
checkStatus(response, 204, 202);
return new RestResult(response);
}
Response response = this.request(() -> {
WebTarget webTarget = target.get();
Builder builder = id == null ? webTarget.path(path).request() :
webTarget.path(path).path(encode(id)).request();
this.attachAuthToRequest(builder);
return builder.delete();
});
checkStatus(response, Response.Status.NO_CONTENT,
Response.Status.ACCEPTED);
return new RestResult(response);
}
protected abstract void checkStatus(Response response, int... statuses);
@SneakyThrows
protected Response request(Request.Builder requestBuilder) {
return this.client.newCall(requestBuilder.build()).execute();
}
@SneakyThrows
@Override
public void close() {
if (this.pool != null) {
this.pool.close();
this.cleanExecutor.shutdownNow();
if (this.client != null) {
this.client.dispatcher().executorService().shutdown();
this.client.connectionPool().evictAll();
if (this.client.cache() != null) {
this.client.cache().close();
}
}
this.client.close();
}
private final ThreadLocal<String> authContext =
new InheritableThreadLocal<>();
public void setAuthContext(String auth) {
this.authContext.set(auth);
}
public void resetAuthContext() {
@ -360,132 +406,39 @@ public abstract class AbstractRestClient implements RestClient {
return this.authContext.get();
}
private void attachAuthToRequest(Builder builder) {
public void setAuthContext(String auth) {
this.authContext.set(auth);
}
private void attachAuthToRequest(Request.Builder builder) {
// Add auth header
String auth = this.getAuthContext();
if (StringUtils.isNotEmpty(auth)) {
builder.header(HttpHeaders.AUTHORIZATION, auth);
builder.addHeader(RestHeaders.AUTHORIZATION, auth);
}
}
private Pair<Builder, Entity<?>> buildRequest(
String path, String id, Object object,
MultivaluedMap<String, Object> headers,
Map<String, Object> params) {
WebTarget target = this.target;
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, Object> param : params.entrySet()) {
target = target.queryParam(param.getKey(), param.getValue());
}
@SneakyThrows
private X509TrustManager trustManagerForCertificates(String trustStoreFile,
String trustStorePass) {
char[] password = trustStorePass.toCharArray();
// load keyStore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (FileInputStream in = new FileInputStream(trustStoreFile)) {
keyStore.load(in, password);
}
Builder builder = id == null ? target.path(path).request() :
target.path(path).path(encode(id)).request();
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
String encoding = null;
if (headers != null && !headers.isEmpty()) {
// Add headers
builder = builder.headers(headers);
encoding = (String) headers.getFirst("Content-Encoding");
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" +
Arrays.toString(trustManagers));
}
// Add auth header
this.attachAuthToRequest(builder);
/*
* We should specify the encoding of the entity object manually,
* because Entity.json() method will reset "content encoding =
* null" that has been set up by headers before.
*/
MediaType customContentType = parseCustomContentType(headers);
Entity<?> entity;
if (encoding == null) {
entity = Entity.entity(object, customContentType);
} else {
Variant variant = new Variant(customContentType,
(String) null, encoding);
entity = Entity.entity(object, variant);
}
return Pair.of(builder, entity);
}
/**
* parse user custom content-type, returns MediaType.APPLICATION_JSON_TYPE default.
* @param headers custom http header
*/
private static MediaType parseCustomContentType(MultivaluedMap<String, Object> headers) {
String customContentType = null;
if (MapUtils.isNotEmpty(headers) && headers.get("Content-Type") != null) {
List<?> contentTypeObj = headers.get("Content-Type");
if (contentTypeObj != null && !contentTypeObj.isEmpty()) {
customContentType = contentTypeObj.get(0).toString();
}
return MediaType.valueOf(customContentType);
}
return MediaType.APPLICATION_JSON_TYPE;
}
private static void configConnectionManager(String url, ClientConfig conf) {
/*
* Using httpclient with connection pooling, and configuring the
* jersey connector. But the jersey that has been released in the maven central
* repository seems to have a bug: https://github.com/jersey/jersey/pull/3752
*/
PoolingHttpClientConnectionManager pool = connectionManager(url, conf);
Object maxTotal = conf.getProperty("maxTotal");
Object maxPerRoute = conf.getProperty("maxPerRoute");
if (maxTotal != null) {
pool.setMaxTotal((int) maxTotal);
}
if (maxPerRoute != null) {
pool.setDefaultMaxPerRoute((int) maxPerRoute);
}
conf.property(ApacheClientProperties.CONNECTION_MANAGER, pool);
conf.connectorProvider(new ApacheConnectorProvider());
}
private static PoolingHttpClientConnectionManager connectionManager(
String url,
ClientConfig conf) {
String protocol = (String) conf.getProperty("protocol");
if (protocol == null || "http".equals(protocol)) {
return new PoolingHttpClientConnectionManager(TTL, TimeUnit.HOURS);
}
assert "https".equals(protocol);
String trustStoreFile = (String) conf.getProperty("trustStoreFile");
E.checkArgument(trustStoreFile != null && !trustStoreFile.isEmpty(),
"The trust store file must be set when use https");
String trustStorePass = (String) conf.getProperty("trustStorePassword");
E.checkArgument(trustStorePass != null,
"The trust store password must be set when use https");
SSLContext context = SslConfigurator.newInstance()
.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePass)
.securityProtocol("SSL")
.createSSLContext();
TrustManager[] trustAllManager = NoCheckTrustManager.create();
try {
context.init(null, trustAllManager, new SecureRandom());
} catch (KeyManagementException e) {
throw new ClientException("Failed to init security management", e);
}
HostnameVerifier verifier = new HostNameVerifier(url);
ConnectionSocketFactory httpSocketFactory, httpsSocketFactory;
httpSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
httpsSocketFactory = new SSLConnectionSocketFactory(context, verifier);
Registry<ConnectionSocketFactory> registry =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", httpSocketFactory)
.register("https", httpsSocketFactory)
.build();
return new PoolingHttpClientConnectionManager(registry, null,
null, null, TTL,
TimeUnit.HOURS);
}
public static String encode(String raw) {
return UriComponent.encode(raw, UriComponent.Type.PATH_SEGMENT);
return (X509TrustManager) trustManagers[0];
}
public static class HostNameVerifier implements HostnameVerifier {
@ -505,107 +458,9 @@ public abstract class AbstractRestClient implements RestClient {
if (!this.url.isEmpty() && this.url.endsWith(hostname)) {
return true;
} else {
HostnameVerifier verifier = HttpsURLConnection
.getDefaultHostnameVerifier();
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
return verifier.verify(hostname, session);
}
}
}
private static class NoCheckTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public static TrustManager[] create() {
return new TrustManager[]{new NoCheckTrustManager()};
}
}
private static class ConfigBuilder {
private final ClientConfig config;
ConfigBuilder() {
this.config = new ClientConfig();
}
public ConfigBuilder configTimeout(int timeout) {
this.config.property(ClientProperties.CONNECT_TIMEOUT, timeout);
this.config.property(ClientProperties.READ_TIMEOUT, timeout);
return this;
}
public ConfigBuilder configUser(String username, String password) {
/*
* NOTE: don't use non-preemptive mode
* In non-preemptive mode the authentication information is added
* only when server refuses the request with 401 status code and
* then the request is repeated.
* Non-preemptive has negative impact on the performance. The
* advantage is it doesn't send credentials when they are not needed
* https://jersey.github.io/documentation/latest/client.html#d0e5461
*/
this.config.register(HttpAuthenticationFeature.basic(username,
password));
return this;
}
public ConfigBuilder configToken(String token) {
this.config.property(TOKEN_KEY, token);
this.config.register(BearerRequestFilter.class);
return this;
}
public ConfigBuilder configPool(int maxTotal, int maxPerRoute) {
this.config.property("maxTotal", maxTotal);
this.config.property("maxPerRoute", maxPerRoute);
return this;
}
public ConfigBuilder configIdleTime(int idleTime) {
this.config.property("idleTime", idleTime);
return this;
}
public ConfigBuilder configSSL(String trustStoreFile,
String trustStorePassword) {
if (trustStoreFile == null || trustStoreFile.isEmpty() ||
trustStorePassword == null) {
this.config.property("protocol", "http");
} else {
this.config.property("protocol", "https");
}
this.config.property("trustStoreFile", trustStoreFile);
this.config.property("trustStorePassword", trustStorePassword);
return this;
}
public ClientConfig build() {
return this.config;
}
}
public static class BearerRequestFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext context) {
String token = context.getClient().getConfiguration()
.getProperty(TOKEN_KEY).toString();
context.getHeaders().add(HttpHeaders.AUTHORIZATION,
"Bearer " + token);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.rest;
import java.io.IOException;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpBasicAuthInterceptor implements Interceptor {
private final String credentials;
public OkHttpBasicAuthInterceptor(String user, String password) {
this.credentials = Credentials.basic(user, password);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.header(RestHeaders.AUTHORIZATION) == null) {
Request authenticatedRequest = request.newBuilder()
.header(RestHeaders.AUTHORIZATION,
this.credentials)
.build();
return chain.proceed(authenticatedRequest);
}
return chain.proceed(request);
}
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.rest;
import static org.apache.hugegraph.rest.RestHeaders.AUTHORIZATION;
import static org.apache.hugegraph.rest.RestHeaders.BEARER_PREFIX;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpTokenInterceptor implements Interceptor {
private final String token;
public OkHttpTokenInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.header(AUTHORIZATION) == null) {
Request authenticatedRequest = request.newBuilder()
.header(AUTHORIZATION,
BEARER_PREFIX + this.token)
.build();
return chain.proceed(authenticatedRequest);
}
return chain.proceed(request);
}
}

View File

@ -19,31 +19,28 @@ package org.apache.hugegraph.rest;
import java.util.Map;
import jakarta.ws.rs.core.MultivaluedMap;
public interface RestClient {
/**
* Post method
*/
RestResult post(String path, Object object);
RestResult post(String path, Object object, MultivaluedMap<String, Object> headers);
RestResult post(String path, Object object, RestHeaders headers);
RestResult post(String path, Object object, Map<String, Object> params);
RestResult post(String path, Object object, MultivaluedMap<String, Object> headers,
Map<String, Object> params);
RestResult post(String path, Object object, RestHeaders headers, Map<String, Object> params);
/**
* Put method
*/
RestResult put(String path, String id, Object object);
RestResult put(String path, String id, Object object, MultivaluedMap<String, Object> headers);
RestResult put(String path, String id, Object object, RestHeaders headers);
RestResult put(String path, String id, Object object, Map<String, Object> params);
RestResult put(String path, String id, Object object, MultivaluedMap<String, Object> headers,
RestResult put(String path, String id, Object object, RestHeaders headers,
Map<String, Object> params);
/**

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.rest;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Getter
@Setter
public class RestClientConfig {
private String user;
private String password;
private String token;
// unit in milliseconds
private Integer timeout;
private Integer maxConns;
private Integer maxConnsPerRoute;
// unit in seconds
private Integer idleTime = 30;
private Integer maxIdleConns = 5;
private String trustStoreFile;
private String trustStorePassword;
}

View File

@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.rest;
import java.util.Date;
import java.util.Iterator;
import kotlin.Pair;
public class RestHeaders {
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_ENCODING = "Content-Encoding";
public static final String AUTHORIZATION = "Authorization";
public static final String APPLICATION_JSON = "application/json";
public static final String BEARER_PREFIX = "Bearer ";
private final okhttp3.Headers.Builder headersBuilder;
public RestHeaders() {
this.headersBuilder = new okhttp3.Headers.Builder();
}
public static RestHeaders convertToRestHeaders(okhttp3.Headers headers) {
RestHeaders restHeaders = new RestHeaders();
if (headers != null) {
Iterator<Pair<String, String>> iter = headers.iterator();
while (iter.hasNext()) {
Pair<String, String> pair = iter.next();
restHeaders.add(pair.getFirst(), pair.getSecond());
}
}
return restHeaders;
}
public String get(String key) {
return this.headersBuilder.get(key);
}
public Date getDate(String key) {
return this.headersBuilder.build().getDate(key);
}
public RestHeaders add(String key, String value) {
this.headersBuilder.add(key, value);
return this;
}
public RestHeaders add(String key, Date value) {
this.headersBuilder.add(key, value);
return this;
}
@Override
public int hashCode() {
return this.toOkHttpHeader().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof RestHeaders) {
return this.toOkHttpHeader().equals(((RestHeaders) obj).toOkHttpHeader());
}
return false;
}
public okhttp3.Headers toOkHttpHeader() {
return this.headersBuilder.build();
}
}

View File

@ -21,39 +21,47 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import okhttp3.Response;
public class RestResult {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final int status;
private final MultivaluedMap<String, Object> headers;
private final RestHeaders headers;
private final String content;
public RestResult(Response response) {
this(response.getStatus(), response.readEntity(String.class),
response.getHeaders());
this(response.code(), getResponseContent(response),
RestHeaders.convertToRestHeaders(response.headers()));
}
public RestResult(int status, String content,
MultivaluedMap<String, Object> headers) {
public RestResult(int status, String content, RestHeaders headers) {
this.status = status;
this.headers = headers;
this.content = content;
}
@SneakyThrows
private static String getResponseContent(Response response) {
return response.body().string();
}
public static void registerModule(Module module) {
MAPPER.registerModule(module);
}
public int status() {
return this.status;
}
public MultivaluedMap<String, Object> headers() {
public RestHeaders headers() {
return this.headers;
}
@ -65,8 +73,7 @@ public class RestResult {
try {
return MAPPER.readValue(this.content, clazz);
} catch (Exception e) {
throw new SerializeException(
"Failed to deserialize: %s", e, this.content);
throw new SerializeException("Failed to deserialize: %s", e, this.content);
}
}
@ -76,16 +83,14 @@ public class RestResult {
JsonNode root = MAPPER.readTree(this.content);
JsonNode element = root.get(key);
if (element == null) {
throw new SerializeException(
"Can't find value of the key: %s in json.", key);
throw new SerializeException("Can't find value of the key: %s in json.", key);
}
JavaType type = MAPPER.getTypeFactory()
.constructParametrizedType(ArrayList.class,
List.class, clazz);
return MAPPER.convertValue(element, type);
} catch (IOException e) {
throw new SerializeException(
"Failed to deserialize %s", e, this.content);
throw new SerializeException("Failed to deserialize %s", e, this.content);
}
}
@ -97,18 +102,13 @@ public class RestResult {
try {
return MAPPER.readValue(this.content, type);
} catch (IOException e) {
throw new SerializeException(
"Failed to deserialize %s", e, this.content);
throw new SerializeException("Failed to deserialize %s", e, this.content);
}
}
@Override
public String toString() {
return String.format("{status=%s, headers=%s, content=%s}",
this.status, this.headers, this.content);
}
public static void registerModule(Module module) {
MAPPER.registerModule(module);
return String.format("{status=%s, headers=%s, content=%s}", this.status, this.headers,
this.content);
}
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hugegraph.rest.SerializeException;
import java.io.IOException;
public final class JsonUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void registerModule(Module module) {
MAPPER.registerModule(module);
}
public static String toJson(Object object) {
try {
return MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new SerializeException("Failed to serialize object '%s'",
e, object);
}
}
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return MAPPER.readValue(json, clazz);
} catch (IOException e) {
throw new SerializeException("Failed to deserialize json '%s'",
e, json);
}
}
public static <T> T convertValue(JsonNode node, Class<T> clazz) {
try {
return MAPPER.convertValue(node, clazz);
} catch (IllegalArgumentException e) {
throw new SerializeException("Failed to deserialize json node '%s'",
e, node);
}
}
}

View File

@ -24,5 +24,5 @@ public class CommonVersion {
public static final String NAME = "hugegraph-common";
// The second parameter of Version.of() is for all-in-one JAR
public static final Version VERSION = Version.of(CommonVersion.class, "1.0.1");
public static final Version VERSION = Version.of(CommonVersion.class, "1.2.0");
}

View File

@ -21,249 +21,90 @@ import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import jakarta.ws.rs.client.Invocation.Builder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import org.apache.hugegraph.rest.AbstractRestClient;
import org.apache.hugegraph.rest.ClientException;
import org.apache.hugegraph.rest.RestClient;
import org.apache.hugegraph.rest.RestClientConfig;
import org.apache.hugegraph.rest.RestHeaders;
import org.apache.hugegraph.rest.RestResult;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.testutil.Whitebox;
import org.apache.hugegraph.unit.BaseUnitTest;
import org.junit.Test;
import org.mockito.Mockito;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
import com.google.common.net.HttpHeaders;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.hugegraph.rest.AbstractRestClient;
import org.apache.hugegraph.rest.ClientException;
import org.apache.hugegraph.rest.RestClient;
import org.apache.hugegraph.rest.RestResult;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.testutil.Whitebox;
import lombok.SneakyThrows;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class RestClientTest {
private static class RestClientImpl extends AbstractRestClient {
private final int status;
private final MultivaluedMap<String, Object> headers;
private final String content;
public RestClientImpl(String url, int timeout, int idleTime,
int maxTotal, int maxPerRoute, int status) {
super(url, timeout, idleTime, maxTotal, maxPerRoute);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, int timeout,
int maxTotal, int maxPerRoute, int status) {
super(url, timeout, maxTotal, maxPerRoute);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String user, String password,
int timeout, int status) {
super(url, user, password, timeout);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String user, String password,
int timeout, int maxTotal, int maxPerRoute,
int status) {
super(url, user, password, timeout, maxTotal, maxPerRoute);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String user, String password,
int timeout, int maxTotal, int maxPerRoute,
String trustStoreFile, String trustStorePassword,
int status) {
super(url, user, password, timeout, maxTotal, maxPerRoute,
trustStoreFile, trustStorePassword);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String token,
int timeout, int status) {
super(url, token, timeout);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String token, int timeout,
int maxTotal, int maxPerRoute, int status) {
super(url, token, timeout, maxTotal, maxPerRoute);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, String token, int timeout,
int maxTotal, int maxPerRoute,
String trustStoreFile,
String trustStorePassword, int status) {
super(url, token, timeout, maxTotal, maxPerRoute,
trustStoreFile, trustStorePassword);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}
public RestClientImpl(String url, int timeout, int status) {
this(url, timeout, status, ImmutableMultivaluedMap.empty(), "");
}
public RestClientImpl(String url, int timeout, int status,
MultivaluedMap<String, Object> headers,
String content) {
super(url, timeout);
this.status = status;
this.headers = headers;
this.content = content;
}
@Override
protected Response request(Callable<Response> method) {
Response response = Mockito.mock(Response.class);
Mockito.when(response.getStatus()).thenReturn(this.status);
Mockito.when(response.getHeaders()).thenReturn(this.headers);
Mockito.when(response.readEntity(String.class))
.thenReturn(this.content);
return response;
}
@Override
protected void checkStatus(Response response,
Response.Status... statuses) {
boolean match = false;
for (Response.Status status : statuses) {
if (status.getStatusCode() == response.getStatus()) {
match = true;
break;
}
}
if (!match) {
throw new ClientException("Invalid response '%s'", response);
}
}
}
private static final String TEST_URL = "http://localhost:8080";
@Test
public void testPost() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
// TODO: How to verify it?
public void testPostWithMaxTotalAndPerRoute() {
RestClient client = new RestClientImpl("/test", 1000, 10, 5, 200);
public void testPostWithMaxConnsAndPerRoute() {
RestClientConfig restClientConfig =
RestClientConfig.builder().timeout(1000).maxConns(10).maxConnsPerRoute(5).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testCleanExecutor() throws Exception {
// Modify IDLE_TIME 100ms to speed test
int newIdleTime = 100;
int newCheckPeriod = newIdleTime + 20;
RestClient client = new RestClientImpl("/test", 1000, newIdleTime,
10, 5, 200);
PoolingHttpClientConnectionManager pool;
pool = Whitebox.getInternalState(client, "pool");
pool = Mockito.spy(pool);
Whitebox.setInternalState(client, "pool", pool);
HttpRoute route = new HttpRoute(HttpHost.create(
"http://127.0.0.1:8080"));
// Create a connection manually, it will be put into leased list
HttpClientConnection conn = pool.requestConnection(route, null)
.get(1L, TimeUnit.SECONDS);
PoolStats stats = pool.getTotalStats();
int usingConns = stats.getLeased() + stats.getPending();
Assert.assertGte(1, usingConns);
// Sleep more than two check periods for busy connection
Thread.sleep(newCheckPeriod);
Mockito.verify(pool, Mockito.never()).closeExpiredConnections();
stats = pool.getTotalStats();
usingConns = stats.getLeased() + stats.getPending();
Assert.assertGte(1, usingConns);
// The connection will be put into available list
pool.releaseConnection(conn, null, 0, TimeUnit.SECONDS);
stats = pool.getTotalStats();
usingConns = stats.getLeased() + stats.getPending();
Assert.assertEquals(0, usingConns);
/*
* Sleep more than two check periods for free connection,
* ensure connection has been closed
*/
Thread.sleep(newCheckPeriod);
Mockito.verify(pool, Mockito.atLeastOnce())
.closeExpiredConnections();
Mockito.verify(pool, Mockito.atLeastOnce())
.closeIdleConnections(newIdleTime, TimeUnit.MILLISECONDS);
}
@Test
public void testPostWithUserAndPassword() {
RestClient client = new RestClientImpl("/test", "user", "", 1000, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().user("user").password("").timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testPostWithToken() {
RestClient client = new RestClientImpl("/test", "token", 1000, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().token("token").timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testPostWithAllParams() {
RestClient client = new RestClientImpl("/test", "user", "", 1000,
10, 5, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().user("user").password("").timeout(1000).maxConns(10)
.maxConnsPerRoute(5).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testPostWithTokenAndAllParams() {
RestClient client = new RestClientImpl("/test", "token", 1000,
10, 5, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().token("token").timeout(1000).maxConns(10)
.maxConnsPerRoute(5).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@ -276,9 +117,11 @@ public class RestClientTest {
BaseUnitTest.downloadFileByUrl(url, trustStoreFile);
String trustStorePassword = "changeit";
RestClient client = new RestClientImpl("/test", "user", "", 1000,
10, 5, trustStoreFile,
trustStorePassword, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().user("user").password("").timeout(1000).maxConns(10)
.maxConnsPerRoute(5).trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@ -291,9 +134,11 @@ public class RestClientTest {
BaseUnitTest.downloadFileByUrl(url, trustStoreFile);
String trustStorePassword = "changeit";
RestClient client = new RestClientImpl("/test", "token", 1000,
10, 5, trustStoreFile,
trustStorePassword, 200);
RestClientConfig restClientConfig =
RestClientConfig.builder().token("token").timeout(1000).maxConns(10)
.maxConnsPerRoute(5).trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}
@ -327,13 +172,12 @@ public class RestClientTest {
@Test
public void testPostWithHeaderAndContent() {
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
headers.add("key1", "value1-1");
headers.add("key1", "value1-2");
headers.add("Content-Encoding", "gzip");
RestHeaders headers = new RestHeaders().add("key1", "value1-1")
.add("key1", "value1-2")
.add("Content-Encoding", "gzip");
String content = "{\"names\": [\"marko\", \"josh\", \"lop\"]}";
RestClient client = new RestClientImpl("/test", 1000, 200,
headers, content);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200, headers, content);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
Assert.assertEquals(headers, restResult.headers());
@ -344,7 +188,8 @@ public class RestClientTest {
@Test
public void testPostWithException() {
RestClient client = new RestClientImpl("/test", 1000, 400);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 400);
Assert.assertThrows(ClientException.class, () -> {
client.post("path", "body");
});
@ -352,8 +197,10 @@ public class RestClientTest {
@Test
public void testPostWithParams() {
RestClient client = new RestClientImpl("/test", 1000, 200);
MultivaluedMap<String, Object> headers = ImmutableMultivaluedMap.empty();
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestHeaders headers = new RestHeaders();
Map<String, Object> params = ImmutableMap.of("param1", "value1");
RestResult restResult = client.post("path", "body", headers,
params);
@ -362,25 +209,27 @@ public class RestClientTest {
@Test
public void testPut() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.put("path", "id1", "body");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testPutWithHeaders() {
RestClient client = new RestClientImpl("/test", 1000, 200);
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
headers.add("key1", "value1-1");
headers.add("key1", "value1-2");
headers.add("Content-Encoding", "gzip");
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestHeaders headers = new RestHeaders().add("key1", "value1-1")
.add("key2", "value1-2")
.add("Content-Encoding", "gzip");
RestResult restResult = client.put("path", "id1", "body", headers);
Assert.assertEquals(200, restResult.status());
}
@Test
public void testPutWithParams() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
Map<String, Object> params = ImmutableMap.of("param1", "value1");
RestResult restResult = client.put("path", "id1", "body", params);
Assert.assertEquals(200, restResult.status());
@ -388,7 +237,8 @@ public class RestClientTest {
@Test
public void testPutWithException() {
RestClient client = new RestClientImpl("/test", 1000, 400);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 400);
Assert.assertThrows(ClientException.class, () -> {
client.put("path", "id1", "body");
});
@ -396,21 +246,24 @@ public class RestClientTest {
@Test
public void testGet() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.get("path");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testGetWithId() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
RestResult restResult = client.get("path", "id1");
Assert.assertEquals(200, restResult.status());
}
@Test
public void testGetWithParams() {
RestClient client = new RestClientImpl("/test", 1000, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200);
Map<String, Object> params = new HashMap<>();
params.put("key1", ImmutableList.of("value1-1", "value1-2"));
params.put("key2", "value2");
@ -420,7 +273,8 @@ public class RestClientTest {
@Test
public void testGetWithException() {
RestClient client = new RestClientImpl("/test", 1000, 400);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 400);
Assert.assertThrows(ClientException.class, () -> {
client.get("path", "id1");
});
@ -428,14 +282,16 @@ public class RestClientTest {
@Test
public void testDeleteWithId() {
RestClient client = new RestClientImpl("/test", 1000, 204);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 204);
RestResult restResult = client.delete("path", "id1");
Assert.assertEquals(204, restResult.status());
}
@Test
public void testDeleteWithParams() {
RestClient client = new RestClientImpl("/test", 1000, 204);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 204);
Map<String, Object> params = ImmutableMap.of("param1", "value1");
RestResult restResult = client.delete("path", params);
Assert.assertEquals(204, restResult.status());
@ -443,38 +299,17 @@ public class RestClientTest {
@Test
public void testDeleteWithException() {
RestClient client = new RestClientImpl("/test", 1000, 400);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 400);
Assert.assertThrows(ClientException.class, () -> {
client.delete("path", "id1");
});
}
@Test
public void testClose() {
RestClient client = new RestClientImpl("/test", 1000, 10, 5, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
client.close();
Assert.assertThrows(IllegalStateException.class, () -> {
client.post("path", "body");
});
PoolingHttpClientConnectionManager pool;
pool = Whitebox.getInternalState(client, "pool");
Assert.assertNotNull(pool);
AtomicBoolean isShutDown = Whitebox.getInternalState(pool, "isShutDown");
Assert.assertTrue(isShutDown.get());
ScheduledExecutorService cleanExecutor;
cleanExecutor = Whitebox.getInternalState(client, "cleanExecutor");
Assert.assertNotNull(cleanExecutor);
Assert.assertTrue(cleanExecutor.isShutdown());
}
@Test
public void testAuthContext() {
RestClientImpl client = new RestClientImpl("/test", 1000, 10, 5, 200);
RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build();
RestClientImpl client = new RestClientImpl(TEST_URL, restClientConfig, 200);
Assert.assertNull(client.getAuthContext());
String token = UUID.randomUUID().toString();
@ -485,48 +320,33 @@ public class RestClientTest {
Assert.assertNull(client.getAuthContext());
}
private static class MockRestClientImpl extends AbstractRestClient {
public MockRestClientImpl(String url, int timeout) {
super(url, timeout);
}
@Override
protected void checkStatus(Response response,
Response.Status... statuses) {
// pass
}
}
@SneakyThrows
@Test
public void testRequest() {
MockRestClientImpl client = new MockRestClientImpl("test", 1000);
Response response = Mockito.mock(Response.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(response.code()).thenReturn(200);
Mockito.when(response.headers())
.thenReturn(new RestHeaders().toOkHttpHeader());
Mockito.when(response.body().string()).thenReturn("content");
WebTarget target = Mockito.mock(WebTarget.class);
Builder builder = Mockito.mock(Builder.class);
Request.Builder requestBuilder = Mockito.mock(Request.Builder.class,
Mockito.RETURNS_DEEP_STUBS);
Mockito.when(requestBuilder.delete()).thenReturn(requestBuilder);
Mockito.when(requestBuilder.get()).thenReturn(requestBuilder);
Mockito.when(requestBuilder.put(Mockito.any())).thenReturn(requestBuilder);
Mockito.when(requestBuilder.post(Mockito.any())).thenReturn(requestBuilder);
Mockito.when(requestBuilder.url((HttpUrl) Mockito.any())).thenReturn(requestBuilder);
MockRestClientImpl client = new MockRestClientImpl(TEST_URL, 1000) {
@Override
protected Request.Builder newRequestBuilder() {
return requestBuilder;
}
};
Mockito.when(target.path("test")).thenReturn(target);
Mockito.when(target.path("test")
.path(AbstractRestClient.encode("id")))
.thenReturn(target);
Mockito.when(target.path("test").request()).thenReturn(builder);
Mockito.when(target.path("test")
.path(AbstractRestClient.encode("id"))
.request())
.thenReturn(builder);
OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(okHttpClient.newCall(Mockito.any()).execute()).thenReturn(response);
Response response = Mockito.mock(Response.class);
Mockito.when(response.getStatus()).thenReturn(200);
Mockito.when(response.getHeaders())
.thenReturn(new MultivaluedHashMap<>());
Mockito.when(response.readEntity(String.class)).thenReturn("content");
Mockito.when(builder.delete()).thenReturn(response);
Mockito.when(builder.get()).thenReturn(response);
Mockito.when(builder.put(Mockito.any())).thenReturn(response);
Mockito.when(builder.post(Mockito.any())).thenReturn(response);
Whitebox.setInternalState(client, "target", target);
Whitebox.setInternalState(client, "client", okHttpClient);
RestResult result;
@ -534,97 +354,140 @@ public class RestClientTest {
client.setAuthContext("token1");
result = client.delete("test", ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token1");
Mockito.verify(requestBuilder).addHeader(RestHeaders.AUTHORIZATION, "token1");
client.resetAuthContext();
client.setAuthContext("token2");
result = client.delete("test", "id");
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token2");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token2");
client.resetAuthContext();
// Test get
client.setAuthContext("token3");
result = client.get("test");
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token3");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token3");
client.resetAuthContext();
client.setAuthContext("token4");
result = client.get("test", ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token4");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token4");
client.resetAuthContext();
client.setAuthContext("token5");
result = client.get("test", "id");
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token5");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token5");
client.resetAuthContext();
// Test put
client.setAuthContext("token6");
result = client.post("test", new Object());
result = client.post("test", null);
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token6");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token6");
client.resetAuthContext();
client.setAuthContext("token7");
result = client.post("test", new Object(), new MultivaluedHashMap<>());
result = client.post("test", null, new RestHeaders());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token7");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token7");
client.resetAuthContext();
client.setAuthContext("token8");
result = client.post("test", new Object(), ImmutableMap.of());
result = client.post("test", null, ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token8");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token8");
client.resetAuthContext();
client.setAuthContext("token9");
result = client.post("test", new Object(), new MultivaluedHashMap<>(),
result = client.post("test", null, new RestHeaders(),
ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token9");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token9");
client.resetAuthContext();
// Test post
client.setAuthContext("token10");
result = client.post("test", new Object());
result = client.post("test", null);
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token10");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token10");
client.resetAuthContext();
client.setAuthContext("token11");
result = client.post("test", new Object(), new MultivaluedHashMap<>());
result = client.post("test", null, new RestHeaders());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token11");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token11");
client.resetAuthContext();
client.setAuthContext("token12");
result = client.post("test", new Object(), ImmutableMap.of());
result = client.post("test", null, ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token12");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token12");
client.resetAuthContext();
client.setAuthContext("token13");
result = client.post("test", new Object(), new MultivaluedHashMap<>(),
result = client.post("test", null, new RestHeaders(),
ImmutableMap.of());
Assert.assertEquals(200, result.status());
Mockito.verify(builder).header(HttpHeaders.AUTHORIZATION,
"token13");
Mockito.verify(requestBuilder).addHeader(HttpHeaders.AUTHORIZATION, "token13");
client.resetAuthContext();
}
private static class RestClientImpl extends AbstractRestClient {
private final int status;
private final RestHeaders headers;
private final String content;
public RestClientImpl(String url, RestClientConfig config, int status) {
this(url, config, status, new RestHeaders(), "");
}
public RestClientImpl(String url, RestClientConfig config, int status, RestHeaders headers,
String content) {
super(url, config);
this.status = status;
this.headers = headers;
this.content = content;
}
@SneakyThrows
@Override
protected Response request(Request.Builder requestBuilder) {
Response response = Mockito.mock(Response.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(response.code()).thenReturn(this.status);
Mockito.when(response.headers()).thenReturn(this.headers.toOkHttpHeader());
Mockito.when(response.body().string()).thenReturn(this.content);
return response;
}
@Override
protected void checkStatus(Response response, int... statuses) {
boolean match = false;
for (int status : statuses) {
if (status == response.code()) {
match = true;
break;
}
}
if (!match) {
throw new ClientException("Invalid response '%s'", response);
}
}
}
private static class MockRestClientImpl extends AbstractRestClient {
public MockRestClientImpl(String url, int timeout) {
super(url, timeout);
}
@Override
protected void checkStatus(Response response, int... statuses) {
// pass
}
}
}

View File

@ -19,22 +19,44 @@ package org.apache.hugegraph.unit.rest;
import java.util.Map;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
import org.apache.hugegraph.rest.RestHeaders;
import org.apache.hugegraph.rest.RestResult;
import org.apache.hugegraph.rest.SerializeException;
import org.apache.hugegraph.testutil.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.hugegraph.rest.RestResult;
import org.apache.hugegraph.rest.SerializeException;
import org.apache.hugegraph.testutil.Assert;
import lombok.SneakyThrows;
import okhttp3.Response;
public class RestResultTest {
private static RestResult newRestResult(int status) {
return newRestResult(status, "", new RestHeaders());
}
private static RestResult newRestResult(int status, String content) {
return newRestResult(status, content, new RestHeaders());
}
private static RestResult newRestResult(int status, RestHeaders headers) {
return newRestResult(status, "", headers);
}
@SneakyThrows
private static RestResult newRestResult(int status, String content,
RestHeaders headers) {
Response response = Mockito.mock(Response.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(response.code()).thenReturn(status);
Mockito.when(response.headers()).thenReturn(headers.toOkHttpHeader());
Mockito.when(response.body().string())
.thenReturn(content);
return new RestResult(response);
}
@Test
public void testStatus() {
RestResult result = newRestResult(200);
@ -43,7 +65,7 @@ public class RestResultTest {
@Test
public void testHeaders() {
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
RestHeaders headers = new RestHeaders();
headers.add("key1", "value1-1");
headers.add("key1", "value1-2");
headers.add("key2", "value2");
@ -116,27 +138,4 @@ public class RestResultTest {
result3.readList(String.class);
});
}
private static RestResult newRestResult(int status) {
return newRestResult(status, "", ImmutableMultivaluedMap.empty());
}
private static RestResult newRestResult(int status, String content) {
return newRestResult(status, content, ImmutableMultivaluedMap.empty());
}
private static RestResult newRestResult(int status,
MultivaluedMap<String, Object> h) {
return newRestResult(status, "", h);
}
private static RestResult newRestResult(int status, String content,
MultivaluedMap<String, Object> h) {
Response response = Mockito.mock(Response.class);
Mockito.when(response.getStatus()).thenReturn(status);
Mockito.when(response.getHeaders()).thenReturn(h);
Mockito.when(response.readEntity(String.class))
.thenReturn(content);
return new RestResult(response);
}
}

View File

@ -94,7 +94,7 @@ public class ReflectionUtilTest extends BaseUnitTest {
@SuppressWarnings("unchecked")
List<ClassInfo> classes = IteratorUtils.toList(ReflectionUtil.classes(
"org.apache.hugegraph.util"));
Assert.assertEquals(17, classes.size());
Assert.assertEquals(18, classes.size());
classes.sort(Comparator.comparing(ClassInfo::getName));
Assert.assertEquals("org.apache.hugegraph.util.Bytes",
classes.get(0).getName());
@ -103,7 +103,7 @@ public class ReflectionUtilTest extends BaseUnitTest {
Assert.assertEquals("org.apache.hugegraph.util.CollectionUtil",
classes.get(2).getName());
Assert.assertEquals("org.apache.hugegraph.util.VersionUtil",
classes.get(16).getName());
classes.get(17).getName());
}
@Test

View File

@ -0,0 +1,17 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
# 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.
#
mock-maker-inline

View File

@ -1,6 +1,6 @@
animal-sniffer-annotations-1.18.jar
annotations-4.1.1.4.jar
aopalliance-repackaged-3.0.1.jar
annotations-13.0.jar
bolt-1.6.2.jar
checker-qual-3.5.0.jar
commons-beanutils-1.9.4.jar
@ -27,11 +27,6 @@ gson-2.8.6.jar
guava-30.0-jre.jar
hamcrest-core-1.3.jar
hessian-3.3.7.jar
hk2-api-3.0.1.jar
hk2-locator-3.0.1.jar
hk2-utils-3.0.1.jar
httpclient-4.5.13.jar
httpcore-4.4.13.jar
j2objc-annotations-1.3.jar
jackson-annotations-2.14.0-rc1.jar
jackson-core-2.14.0-rc1.jar
@ -42,22 +37,10 @@ jackson-jaxrs-json-provider-2.14.0-rc1.jar
jackson-module-jaxb-annotations-2.14.0-rc1.jar
jakarta.activation-2.0.1.jar
jakarta.activation-api-1.2.2.jar
jakarta.annotation-api-2.0.0.jar
jakarta.inject-api-2.0.0.jar
jakarta.ws.rs-api-3.0.0.jar
jakarta.xml.bind-api-4.0.0-RC2.jar
javassist-3.28.0-GA.jar
javax.activation-api-1.2.0.jar
javax.json-1.0.jar
jaxb-api-2.3.1.jar
jaxb-core-3.0.2.jar
jaxb-impl-3.0.2.jar
jersey-apache-connector-3.0.3.jar
jersey-client-3.0.3.jar
jersey-common-3.0.3.jar
jersey-entity-filtering-3.0.3.jar
jersey-hk2-3.0.3.jar
jersey-media-json-jackson-3.0.3.jar
joda-time-2.10.8.jar
jsr305-3.0.1.jar
junit-4.13.1.jar
@ -71,7 +54,6 @@ opentracing-api-0.22.0.jar
opentracing-mock-0.22.0.jar
opentracing-noop-0.22.0.jar
opentracing-util-0.22.0.jar
osgi-resource-locator-1.0.3.jar
perfmark-api-0.19.0.jar
proto-google-common-protos-1.17.0.jar
protobuf-java-3.11.0.jar
@ -84,3 +66,12 @@ swagger-core-1.5.18.jar
swagger-models-1.5.18.jar
tracer-core-3.0.8.jar
validation-api-1.1.0.Final.jar
kotlin-stdlib-1.6.20.jar
kotlin-stdlib-common-1.5.31.jar
kotlin-stdlib-jdk7-1.6.10.jar
kotlin-stdlib-jdk8-1.6.10.jar
logging-interceptor-4.10.0.jar
lombok-1.18.8.jar
okhttp-4.10.0.jar
okio-jvm-3.0.0.jar

View File

@ -24,5 +24,5 @@ public class RpcVersion {
public static final String NAME = "hugegraph-rpc";
// The second parameter of Version.of() is for all-in-one JAR
public static final Version VERSION = Version.of(RpcVersion.class, "1.0.1");
public static final Version VERSION = Version.of(RpcVersion.class, "1.2.0");
}

View File

@ -90,7 +90,7 @@
<properties>
<!-- Note: We need also update the version in CommonVersion.java & RpcVersion.java now -->
<revision>1.0.1</revision>
<revision>1.2.0</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<top.level.dir>${project.basedir}/..</top.level.dir>
<compiler.source>1.8</compiler.source>