diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0616bf88..cc0cd1aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: "hugegraph-commons ci" on: + workflow_dispatch: push: branches: - master diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml index 9c74ed0a..0485bd1b 100644 --- a/.github/workflows/license-checker.yml +++ b/.github/workflows/license-checker.yml @@ -1,6 +1,7 @@ name: "license checker" on: + workflow_dispatch: push: branches: - master diff --git a/hugegraph-common/pom.xml b/hugegraph-common/pom.xml index 01bf29b1..2010869b 100644 --- a/hugegraph-common/pom.xml +++ b/hugegraph-common/pom.xml @@ -15,8 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - 4.0.0 @@ -32,11 +32,14 @@ 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. + 1.18.8 + 4.10.0 @@ -196,76 +199,6 @@ ${jackson.version} - - - org.glassfish.jersey.core - jersey-client - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-json-jackson - ${jersey.version} - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-base - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - - - jackson-annotations - com.fasterxml.jackson.core - - - jackson-databind - com.fasterxml.jackson.core - - - jackson-module-jaxb-annotations - com.fasterxml.jackson.module - - - - - org.glassfish.jersey.connectors - jersey-apache-connector - ${jersey.version} - - - commons-codec - commons-codec - - - commons-logging - commons-logging - - - - - org.glassfish.jersey.inject - jersey-hk2 - ${jersey.hk2.version} - - - javassist - org.javassist - - - - - jakarta.xml.bind - jakarta.xml.bind-api - ${jakarta.xml.version} - - - jakarta.activation-api - jakarta.activation - - - com.sun.xml.bind jaxb-impl @@ -278,8 +211,36 @@ + + + com.squareup.okhttp3 + okhttp + + + com.squareup.okhttp3 + logging-interceptor + + + + org.projectlombok + lombok + ${lombok.version} + true + + + + + com.squareup.okhttp3 + okhttp-bom + ${okhttp.version} + pom + import + + + + diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/AbstractRestClient.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/AbstractRestClient.java index ea7e923e..27d9add5 100644 --- a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/AbstractRestClient.java +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/AbstractRestClient.java @@ -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 static final String TOKEN_KEY = "tokenKey"; + private final ThreadLocal authContext; - private final Client client; - private final WebTarget target; + private final OkHttpClient client; - private PoolingHttpClientConnectionManager pool; - private ScheduledExecutorService cleanExecutor; + private final String baseUrl; public AbstractRestClient(String url, int timeout) { - this(url, new ConfigBuilder().configTimeout(timeout).build()); - } - - public AbstractRestClient(String url, String user, String password, - int timeout) { - this(url, new ConfigBuilder().configTimeout(timeout) - .configUser(user, password) - .build()); + this(url, RestClientConfig.builder().timeout(timeout).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 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 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 token, int timeout, + 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, RestClientConfig config) { + this.baseUrl = url; + this.client = buildOkHttpClient(config); + this.authContext = new InheritableThreadLocal<>(); + } + + 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)); - 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()); + if (headers != null && + "gzip".equals(headers.get(RestHeaders.CONTENT_ENCODING))) { + requestBody = gzipBody(requestBody); + } + return requestBody; } - public AbstractRestClient(String url, String token, int timeout) { - this(url, new ConfigBuilder().configTimeout(timeout) - .configToken(token) - .build()); - } + private static RequestBody gzipBody(final RequestBody body) { + return new RequestBody() { + @Override + public MediaType contentType() { + return body.contentType(); + } - public AbstractRestClient(String url, String token, int timeout, - int maxTotal, int maxPerRoute) { - this(url, new ConfigBuilder().configTimeout(timeout) - .configToken(token) - .configPool(maxTotal, maxPerRoute) - .build()); + @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(); + } + }; } - 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, 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 String parseContentType(RestHeaders headers) { + if (headers != null) { + String contentType = headers.get(RestHeaders.CONTENT_TYPE); + if (contentType != null) { + return contentType; + } } + return RestHeaders.APPLICATION_JSON; } - protected abstract void checkStatus(Response response, - Response.Status... statuses); + 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; + } - protected Response request(Callable method) { - try { - return method.call(); - } catch (Exception e) { - throw new ClientException("Failed to do request", e); + @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 RestResult post(String path, Object object) { } @Override - public RestResult post(String path, Object object, - MultivaluedMap 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 params) { + public RestResult post(String path, Object object, Map params) { return this.post(path, object, null, params); } + private Request.Builder genRequestBuilder(String path, String id, RestHeaders headers, + Map 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 headers, + public RestResult post(String path, Object object, RestHeaders headers, Map params) { - Pair> 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 RestResult put(String path, String id, Object object) { } @Override - public RestResult put(String path, String id, Object object, - MultivaluedMap 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 params) { + public RestResult put(String path, String id, Object object, Map params) { return this.put(path, id, object, null, params); } + @SneakyThrows @Override public RestResult put(String path, String id, Object object, - MultivaluedMap headers, + RestHeaders headers, Map params) { - Pair> 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 RestResult get(String path, String id) { return this.get(path, id, ImmutableMap.of()); } + @SneakyThrows private RestResult get(String path, String id, Map params) { - Ref 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); - 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); + try (Response response = request(requestBuilder)) { + checkStatus(response, 200); + return new RestResult(response); + } } @Override @@ -316,40 +367,35 @@ public RestResult delete(String path, String id) { return this.delete(path, id, ImmutableMap.of()); } + @SneakyThrows private RestResult delete(String path, String id, Map params) { - Ref 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(); - }); + protected abstract void checkStatus(Response response, int... statuses); - checkStatus(response, Response.Status.NO_CONTENT, - Response.Status.ACCEPTED); - return new RestResult(response); + @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 authContext = - new InheritableThreadLocal<>(); - - public void setAuthContext(String auth) { - this.authContext.set(auth); } public void resetAuthContext() { @@ -360,132 +406,39 @@ public String getAuthContext() { 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> buildRequest( - String path, String id, Object object, - MultivaluedMap headers, - Map params) { - WebTarget target = this.target; - if (params != null && !params.isEmpty()) { - for (Map.Entry param : params.entrySet()) { - target = target.queryParam(param.getKey(), param.getValue()); - } - } - - Builder builder = id == null ? target.path(path).request() : - target.path(path).path(encode(id)).request(); - - String encoding = null; - if (headers != null && !headers.isEmpty()) { - // Add headers - builder = builder.headers(headers); - encoding = (String) headers.getFirst("Content-Encoding"); - } - // 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); - } + @SneakyThrows + private X509TrustManager trustManagerForCertificates(String trustStoreFile, + String trustStorePass) { + char[] password = trustStorePass.toCharArray(); - /** - * parse user custom content-type, returns MediaType.APPLICATION_JSON_TYPE default. - * @param headers custom http header - */ - private static MediaType parseCustomContentType(MultivaluedMap 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); + // load keyStore + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (FileInputStream in = new FileInputStream(trustStoreFile)) { + keyStore.load(in, password); } - 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); - } + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); - 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); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); } - - HostnameVerifier verifier = new HostNameVerifier(url); - ConnectionSocketFactory httpSocketFactory, httpsSocketFactory; - httpSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); - httpsSocketFactory = new SSLConnectionSocketFactory(context, verifier); - Registry registry = - RegistryBuilder.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 boolean verify(String hostname, SSLSession session) { 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); - } - } } diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpBasicAuthInterceptor.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpBasicAuthInterceptor.java new file mode 100644 index 00000000..f7b1509f --- /dev/null +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpBasicAuthInterceptor.java @@ -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); + } +} diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpTokenInterceptor.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpTokenInterceptor.java new file mode 100644 index 00000000..f5640332 --- /dev/null +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/OkHttpTokenInterceptor.java @@ -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); + } +} diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClient.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClient.java index dc3b7b74..d5b58d9f 100644 --- a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClient.java +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClient.java @@ -19,31 +19,28 @@ 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 headers); + RestResult post(String path, Object object, RestHeaders headers); RestResult post(String path, Object object, Map params); - RestResult post(String path, Object object, MultivaluedMap headers, - Map params); + RestResult post(String path, Object object, RestHeaders headers, Map params); /** * Put method */ RestResult put(String path, String id, Object object); - RestResult put(String path, String id, Object object, MultivaluedMap headers); + RestResult put(String path, String id, Object object, RestHeaders headers); RestResult put(String path, String id, Object object, Map params); - RestResult put(String path, String id, Object object, MultivaluedMap headers, + RestResult put(String path, String id, Object object, RestHeaders headers, Map params); /** diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClientConfig.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClientConfig.java new file mode 100644 index 00000000..ef3e9b0e --- /dev/null +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestClientConfig.java @@ -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; +} diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestHeaders.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestHeaders.java new file mode 100644 index 00000000..03c082ed --- /dev/null +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestHeaders.java @@ -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> iter = headers.iterator(); + while (iter.hasNext()) { + Pair 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(); + } +} diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestResult.java b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestResult.java index 82b7f39a..0aa482b0 100644 --- a/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestResult.java +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/rest/RestResult.java @@ -21,39 +21,47 @@ 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 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 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 headers() { + public RestHeaders headers() { return this.headers; } @@ -65,8 +73,7 @@ public T readObject(Class clazz) { 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 List readList(String key, Class clazz) { 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 List readList(Class clazz) { 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); } } diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/util/JsonUtil.java b/hugegraph-common/src/main/java/org/apache/hugegraph/util/JsonUtil.java new file mode 100644 index 00000000..fc9586b3 --- /dev/null +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/util/JsonUtil.java @@ -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 fromJson(String json, Class clazz) { + try { + return MAPPER.readValue(json, clazz); + } catch (IOException e) { + throw new SerializeException("Failed to deserialize json '%s'", + e, json); + } + } + + public static T convertValue(JsonNode node, Class clazz) { + try { + return MAPPER.convertValue(node, clazz); + } catch (IllegalArgumentException e) { + throw new SerializeException("Failed to deserialize json node '%s'", + e, node); + } + } +} diff --git a/hugegraph-common/src/main/java/org/apache/hugegraph/version/CommonVersion.java b/hugegraph-common/src/main/java/org/apache/hugegraph/version/CommonVersion.java index bcdad926..a049ff44 100644 --- a/hugegraph-common/src/main/java/org/apache/hugegraph/version/CommonVersion.java +++ b/hugegraph-common/src/main/java/org/apache/hugegraph/version/CommonVersion.java @@ -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"); } diff --git a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestClientTest.java b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestClientTest.java index eb9c7c29..f7b998df 100644 --- a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestClientTest.java +++ b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestClientTest.java @@ -21,249 +21,90 @@ 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.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 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.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; -public class RestClientTest { - - private static class RestClientImpl extends AbstractRestClient { - - private final int status; - private final MultivaluedMap 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(), ""); - } +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HttpHeaders; - public RestClientImpl(String url, int timeout, int status, - MultivaluedMap headers, - String content) { - super(url, timeout); - this.status = status; - this.headers = headers; - this.content = content; - } +import lombok.SneakyThrows; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; - @Override - protected Response request(Callable 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; - } +public class RestClientTest { - @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 void testPostHttpsWithAllParams() { 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 void testPostHttpsWithTokenAndAllParams() { 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 void testHostNameVerifier() { @Test public void testPostWithHeaderAndContent() { - MultivaluedMap 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 void testPostWithHeaderAndContent() { @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 void testPostWithException() { @Test public void testPostWithParams() { - RestClient client = new RestClientImpl("/test", 1000, 200); - MultivaluedMap headers = ImmutableMultivaluedMap.empty(); + RestClientConfig restClientConfig = RestClientConfig.builder().timeout(1000).build(); + RestClient client = new RestClientImpl(TEST_URL, restClientConfig, 200); + RestHeaders headers = new RestHeaders(); + Map params = ImmutableMap.of("param1", "value1"); RestResult restResult = client.post("path", "body", headers, params); @@ -362,25 +209,27 @@ public void testPostWithParams() { @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 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 params = ImmutableMap.of("param1", "value1"); RestResult restResult = client.put("path", "id1", "body", params); Assert.assertEquals(200, restResult.status()); @@ -388,7 +237,8 @@ public void testPutWithParams() { @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 void testPutWithException() { @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 params = new HashMap<>(); params.put("key1", ImmutableList.of("value1-1", "value1-2")); params.put("key2", "value2"); @@ -420,7 +273,8 @@ public void testGetWithParams() { @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 void testGetWithException() { @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 params = ImmutableMap.of("param1", "value1"); RestResult restResult = client.delete("path", params); Assert.assertEquals(204, restResult.status()); @@ -443,38 +299,17 @@ public void testDeleteWithParams() { @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 void testAuthContext() { 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); - - WebTarget target = Mockito.mock(WebTarget.class); - Builder builder = Mockito.mock(Builder.class); - - 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); - - 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); + 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"); + + 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; + } + }; + + OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class, Mockito.RETURNS_DEEP_STUBS); + Mockito.when(okHttpClient.newCall(Mockito.any()).execute()).thenReturn(response); + + Whitebox.setInternalState(client, "client", okHttpClient); RestResult result; @@ -534,97 +354,140 @@ public void testRequest() { 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 + } + } } diff --git a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestResultTest.java b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestResultTest.java index 2ae9a4ee..06eb03a1 100644 --- a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestResultTest.java +++ b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/rest/RestResultTest.java @@ -19,22 +19,44 @@ 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 void testStatus() { @Test public void testHeaders() { - MultivaluedMap 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 void testContentListWithException() { 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 h) { - return newRestResult(status, "", h); - } - - private static RestResult newRestResult(int status, String content, - MultivaluedMap 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); - } } diff --git a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/ReflectionUtilTest.java b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/ReflectionUtilTest.java index 510d1454..c83b2ad9 100644 --- a/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/ReflectionUtilTest.java +++ b/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/ReflectionUtilTest.java @@ -94,7 +94,7 @@ public void testClasses() throws IOException { @SuppressWarnings("unchecked") List 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 void testClasses() throws IOException { 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 diff --git a/hugegraph-common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/hugegraph-common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..a76010b2 --- /dev/null +++ b/hugegraph-common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -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 diff --git a/hugegraph-dist/scripts/dependency/known-dependencies.txt b/hugegraph-dist/scripts/dependency/known-dependencies.txt index c6de269c..53b4567d 100644 --- a/hugegraph-dist/scripts/dependency/known-dependencies.txt +++ b/hugegraph-dist/scripts/dependency/known-dependencies.txt @@ -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 + diff --git a/hugegraph-rpc/src/main/java/org/apache/hugegraph/version/RpcVersion.java b/hugegraph-rpc/src/main/java/org/apache/hugegraph/version/RpcVersion.java index f3cf926a..c7ab4052 100644 --- a/hugegraph-rpc/src/main/java/org/apache/hugegraph/version/RpcVersion.java +++ b/hugegraph-rpc/src/main/java/org/apache/hugegraph/version/RpcVersion.java @@ -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"); } diff --git a/pom.xml b/pom.xml index 8f2e39bb..2b8a4866 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ - 1.0.1 + 1.2.0 UTF-8 ${project.basedir}/.. 1.8