From 85e4815238f850f4eb0ecb718860c1e73185d912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Mon, 25 Jul 2022 17:13:18 +0200 Subject: [PATCH 1/8] Refactorings * Get rid of the copy constructors. * Externalize HttpClientHandler. --- README.md | 98 ++++++----- VERSION | 2 +- pom.xml | 2 +- .../client/connection/AbstractAPIHandler.java | 86 ++++++---- .../connection/AbstractVersionAPIHandler.java | 32 ++-- .../connection/GraphConnectionHandler.java | 22 ++- .../DefaultHttpClientHandler.java} | 154 +++++++++--------- .../httpclient/HttpClientHandler.java | 57 +++++++ .../AbstractRemoteAuthTokenAPIHandler.java | 36 +--- .../token/AbstractTokenAPIHandler.java | 18 +- .../token/CodeFlowAuthTokenAPIHandler.java | 21 +-- .../token/EnvironmentTokenAPIHandler.java | 17 +- .../token/FixedTokenAPIHandler.java | 15 +- .../token/PasswordAuthTokenAPIHandler.java | 20 +-- .../client/rest/AuthenticatedAPIHandler.java | 41 ++--- .../client/connection/GenericAPITest.java | 10 +- .../token/InvalidCredentialsAPITest.java | 22 ++- .../hiro/client/rest/AuthAPIHandlerTest.java | 11 +- .../arago/hiro/client/rest/GraphAPITest.java | 11 +- .../hiro/client/rest/TokenAPIHandlerTest.java | 11 +- .../client/websocket/EventWebSocketTest.java | 21 ++- 21 files changed, 350 insertions(+), 357 deletions(-) rename src/main/java/co/arago/hiro/client/connection/{AbstractClientAPIHandler.java => httpclient/DefaultHttpClientHandler.java} (73%) create mode 100644 src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java diff --git a/README.md b/README.md index 1d8fc2b..849427c 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Most of the documentation is done in the sourcecode. ### Straightforward graph example ```Java + import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.rest.GraphAPI; @@ -41,11 +42,11 @@ class Example { public static void main(String[] args) throws HiroException, IOException, InterruptedException { // Build an API handler which takes care of API paths via /api/versions and security tokens. - try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler + .newBuilder() .setRootApiUri(API_URL) .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) - .build() - ) { + .build()) { // Use the actual API you want with the handler GraphAPI graphAPI = GraphAPI.newBuilder(handler) @@ -120,10 +121,10 @@ class Example { public static void main(String[] args) throws HiroException, IOException, InterruptedException { // Build an API handler which takes care of API paths via /api/versions and security tokens. - try (EnvironmentTokenAPIHandler handler = EnvironmentTokenAPIHandler.newBuilder() + try (EnvironmentTokenAPIHandler handler = EnvironmentTokenAPIHandler + .newBuilder() .setRootApiUri(API_URL) - .build() - ) { + .build()) { // Use the actual API you want with the handler GraphAPI graphAPI = GraphAPI.newBuilder(handler) @@ -163,6 +164,7 @@ When you need to access multiple APIs, it is a good idea to share the TokenApiHa unnecessary API version requests and unnecessary token requests with the PasswordAuthTokenAPIHandler for instance. ```java + import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.rest.AuthAPI; @@ -174,11 +176,12 @@ class Example { public static void main(String[] args) throws HiroException, IOException, InterruptedException { // Build an API handler which takes care of API paths via /api/versions and security tokens. - try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler + .newBuilder() .setRootApiUri(API_URL) .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) - .build() - ) { + .build()) { + // Use the actual APIs you want with the handler @@ -196,13 +199,14 @@ class Example { ### Connection sharing When you need to create a token externally, it is a good idea to share a GraphConnectionHandler between -TokenAPIHandlers. The connection, cookies and API version information will be shared among them. The connection will -_not_ be closed when `TokenAPIHandler#close()` is called - you need to close the GraphConnectionHandler explicitly. +TokenAPIHandlers. The connection, cookies and API version information will be shared among them. Be aware, that you +should close the connection via the GraphConnectionHandler here, not via the TokenAPIHandlers. ```java -import co.arago.hiro.client.connection.GraphConnectionHandler; + import co.arago.hiro.client.connection.token.FixedTokenAPIHandler; import co.arago.hiro.client.rest.GraphAPI; +import co.arago.hiro.client.connection.GraphConnectionHandler; import java.io.IOException; @@ -210,18 +214,21 @@ class Example { public static void main(String[] args) throws HiroException, IOException, InterruptedException { // Build a connection without any token handling - try (GraphConnectionHandler connectionHandler = GraphConnectionHandler.newBuilder() + try (GraphConnectionHandler connectionHandler = GraphConnectionHandler + .newBuilder(httpClientHandler) .setRootApiUri(API_URL) - .build() - ) { - GraphAPI graphAPI_user1 = GraphAPI.newBuilder(FixedTokenAPIHandler.newBuilder() - .setSharedConnectionHandler(connectionHandler) - .setToken("TOKEN_1")) + .build()) { + + GraphAPI graphAPI_user1 = GraphAPI.newBuilder( + FixedTokenAPIHandler.newBuilder() + .setSharedConnectionHandler(connectionHandler) + .setToken("TOKEN_1")) .build(); - GraphAPI graphAPI_user2 = GraphAPI.newBuilder(FixedTokenAPIHandler.newBuilder() - .setSharedConnectionHandler(connectionHandler) - .setToken("TOKEN_2")) + GraphAPI graphAPI_user2 = GraphAPI.newBuilder( + FixedTokenAPIHandler.newBuilder() + .setSharedConnectionHandler(connectionHandler) + .setToken("TOKEN_2")) .build(); } @@ -231,14 +238,16 @@ class Example { ### External HTTP Client -An external httpClient can be provided to the TokenAPIHandlers or a GraphConnectionHandler via their Builders -using `setClient(HttpClient client)`. If this is the case, a call to `close()` of such a handler will have no effect. It -has to be closed externally. +An external httpClient can be provided to the HttpClientHandler via its Builder using +`setHttpClient(HttpClient client)`. If this is the case and httpClientAutoClose is not set to `true`, a call +to `close()` +of such a handler will have no effect. Its removal then has to be handled externally. Example with an external httpClient: ```java import co.arago.hiro.client.Config; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.rest.GraphAPI; @@ -246,10 +255,7 @@ import co.arago.hiro.client.model.vertex.HiroVertexListMessage; import java.io.IOException; import java.net.http.HttpClient; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; class Example { public static void main(String[] args) throws HiroException, IOException, InterruptedException { @@ -259,14 +265,19 @@ class Example { .executor(Executors.newFixedThreadPool(1)) .build(); - try { - // Build an API handler which takes care of API paths via /api/versions and security tokens. - // Use an external httpClient. - PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() - .setHttpClient(httpClient) - .setRootApiUri(API_URL) - .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) - .build(); + // Build an API handler which takes care of API paths via /api/versions and security tokens. + // Use external httpClientHandler. + try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler + .newBuilder() + .setHttpClientHandler( + DefaultHttpClientHandler.newBuilder() + .setHttpClient(httpClient) + .setHttpClientAutoClose(true) // If this is missing, the httpClient will not be closed. + .build() + ) + .setRootApiUri(API_URL) + .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) + .build()) { // Use the actual API you want with the handler GraphAPI graphAPI = GraphAPI.newBuilder(handler) @@ -279,22 +290,6 @@ class Example { .execute(); System.out.println(queryResult.toPrettyJsonString()); - } finally { - // Shutdown the connection by shutting down its executorService. - Executor executor = httpClient.executor().orElse(null); - if (executor instanceof ExecutorService) { - ExecutorService executorService = (ExecutorService) executor; - - executorService.shutdown(); - try { - if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - } - - } } } } @@ -488,7 +483,6 @@ class Example { try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() .setRootApiUri(API_URL) .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) - .setAcceptAllCerts(config.accept_all_certs) .build() ) { diff --git a/VERSION b/VERSION index 1d0ba9e..8f0916f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.0 +0.5.0 diff --git a/pom.xml b/pom.xml index 812b333..5bec28a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ co.arago.hiro.client hiro-client-java - 0.4.0 + 0.5.0 arago GmbH diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java index 46d27d7..85c14a3 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java @@ -1,5 +1,7 @@ package co.arago.hiro.client.connection; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; +import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.HiroHttpException; import co.arago.hiro.client.exceptions.RetryException; @@ -15,8 +17,6 @@ import co.arago.util.json.JsonUtil; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -37,8 +37,6 @@ */ public abstract class AbstractAPIHandler { - final static Logger log = LoggerFactory.getLogger(AbstractAPIHandler.class); - // ############################################################################################### // ## Conf and Builder ## // ############################################################################################### @@ -54,6 +52,7 @@ public static abstract class Conf> { private String userAgent; private Long httpRequestTimeout; private int maxRetries; + private HttpClientHandler httpClientHandler; public URI getRootApiURI() { return rootApiURI; @@ -67,7 +66,6 @@ public URI getWebSocketURI() { * @param rootApiURI The root url for the API * @return {@link #self()} * @throws URISyntaxException When the rootApiURI is malformed. - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setRootApiURI(String rootApiURI) throws URISyntaxException { this.rootApiURI = new URI(RegExUtils.removePattern(rootApiURI, "/+$") + "/"); @@ -77,7 +75,6 @@ public T setRootApiURI(String rootApiURI) throws URISyntaxException { /** * @param rootApiURI The root uri for the API * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setRootApiURI(URI rootApiURI) { this.rootApiURI = rootApiURI; @@ -89,7 +86,6 @@ public T setRootApiURI(URI rootApiURI) { * from an apiUrl. * @return {@link #self()} * @throws URISyntaxException When the webSocketURI is a malformed URI. - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setWebSocketURI(String webSocketURI) throws URISyntaxException { this.webSocketURI = new URI(RegExUtils.removePattern(webSocketURI, "/+$") + "/"); @@ -99,7 +95,6 @@ public T setWebSocketURI(String webSocketURI) throws URISyntaxException { /** * @param apiURI The root url for the WebSockets * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setWebSocketURI(URI apiURI) { this.rootApiURI = apiURI; @@ -115,7 +110,6 @@ public String getUserAgent() { * * @param userAgent The line for the User-Agent header. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setUserAgent(String userAgent) { this.userAgent = userAgent; @@ -129,7 +123,6 @@ public Long getHttpRequestTimeout() { /** * @param httpRequestTimeout Request timeout in ms. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setHttpRequestTimeout(Long httpRequestTimeout) { this.httpRequestTimeout = httpRequestTimeout; @@ -143,13 +136,28 @@ public int getMaxRetries() { /** * @param maxRetries Max amount of retries when http errors are received. The default is 0. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ public T setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; return self(); } + public HttpClientHandler getHttpClientHandler() { + return httpClientHandler; + } + + /** + * Sets the httpClientHandler for the backend connection. This handler will be shared among all + * APIHandlers that use this configuration instance. + * + * @param httpClientHandler The connection handler to use. + * @return {@link #self()} + */ + public T setHttpClientHandler(HttpClientHandler httpClientHandler) { + this.httpClientHandler = httpClientHandler; + return self(); + } + protected abstract T self(); public abstract AbstractAPIHandler build(); @@ -163,8 +171,8 @@ public T setMaxRetries(int maxRetries) { public static final String version; static { - version = AbstractClientAPIHandler.class.getPackage().getImplementationVersion(); - String t = AbstractClientAPIHandler.class.getPackage().getImplementationTitle(); + version = DefaultHttpClientHandler.class.getPackage().getImplementationVersion(); + String t = DefaultHttpClientHandler.class.getPackage().getImplementationTitle(); title = (t != null ? t : "hiro-client-java"); } @@ -175,30 +183,48 @@ public T setMaxRetries(int maxRetries) { protected int maxRetries; /** - * Constructor + * This is a reference which will be shared among all AbstractAPIHandler that use the same configuration + * {@link Conf}. + */ + protected HttpClientHandler httpClientHandler; + + /** + * Store the original configuration, so it can be used in other APIHandlers which will use the same underlying + * {@link #httpClientHandler}. + */ + private final Conf conf; + + /** + * Constructor. * * @param builder The builder to use. + * @implNote If the builder does not carry a httpClientHandler, a default will be created here. */ protected AbstractAPIHandler(Conf builder) { + this.conf = builder; this.rootApiURI = notNull(builder.getRootApiURI(), "rootApiURI"); this.webSocketURI = builder.getWebSocketURI(); this.maxRetries = builder.getMaxRetries(); this.httpRequestTimeout = builder.getHttpRequestTimeout(); this.userAgent = builder.getUserAgent() != null ? builder.getUserAgent() : (version != null ? title + " " + version : title); + + this.httpClientHandler = builder.getHttpClientHandler() != null ? builder.getHttpClientHandler() + : DefaultHttpClientHandler.newBuilder().build(); } /** - * Copy constructor + * Return a copy of the configuration. * - * @param other The object to copy the data from. + * @return A copy of the configuration. + * @implNote Please take note, that the included httpClientHandler of this class will be + * added to the returned {@link Conf} and therefore will be shared among all APIHandlers that use this + * configuration. + * @see co.arago.hiro.client.rest.AuthenticatedAPIHandler */ - protected AbstractAPIHandler(AbstractAPIHandler other) { - this.rootApiURI = other.rootApiURI; - this.webSocketURI = other.webSocketURI; - this.maxRetries = other.maxRetries; - this.httpRequestTimeout = other.httpRequestTimeout; - this.userAgent = other.userAgent; + public Conf getConf() { + conf.setHttpClientHandler(httpClientHandler); + return conf; } public URI getRootApiURI() { @@ -213,7 +239,7 @@ public URI getRootApiURI() { public URI getWebSocketURI() { try { return (webSocketURI != null ? webSocketURI - : new URI(RegExUtils.replaceFirst(getRootApiURI().toString(), "^http", "ws"))); + : new URI(RegExUtils.replaceFirst(getRootApiURI().toString(), "^httpclient", "ws"))); } catch (URISyntaxException e) { throw new IllegalArgumentException("Cannot create webSocketURI from rootApiURI.", e); } @@ -269,7 +295,7 @@ public URI buildWebSocketURI(String path) { * @param finalSlash Append a final slash? * @return The constructed URI */ - protected static URI buildURI(URI uri, String path, boolean finalSlash) { + public static URI buildURI(URI uri, String path, boolean finalSlash) { return uri.resolve(RegExUtils.removePattern(path, "^/+") + (finalSlash ? "/" : "")); } @@ -917,18 +943,18 @@ public T patchBinary( // ############################################################################################### /** - * Abstract class that needs to be overwritten by a supplier of a HttpLogger. - * * @return The HttpLogger to use with this class. */ - abstract protected HttpLogger getHttpLogger(); + public HttpLogger getHttpLogger() { + return httpClientHandler.getHttpLogger(); + } /** - * Abstract class that needs to be overwritten by a supplier of a HttpClient. - * * @return The HttpClient to use with this class. */ - abstract protected HttpClient getOrBuildClient(); + public HttpClient getOrBuildClient() { + return httpClientHandler.getOrBuildClient(); + } /** * Override this to add authentication tokens. diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractVersionAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractVersionAPIHandler.java index 9082cfd..dbbcfb3 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractVersionAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractVersionAPIHandler.java @@ -9,26 +9,28 @@ /** * Handles Version information for HIRO. */ -public abstract class AbstractVersionAPIHandler extends AbstractClientAPIHandler { +public abstract class AbstractVersionAPIHandler extends AbstractAPIHandler { // ############################################################################################### // ## Conf and Builder ## // ############################################################################################### - public static abstract class Conf> extends AbstractClientAPIHandler.Conf { + public static abstract class Conf> extends AbstractAPIHandler.Conf { - private AbstractVersionAPIHandler sharedConnectionHandler; + private AbstractVersionAPIHandler sharedConnectionHandler = null; public AbstractVersionAPIHandler getSharedConnectionHandler() { return sharedConnectionHandler; } /** - * Set a shared connection handler. The fields of this class and its parents will be copied from this - * shared handler, overriding other settings. This is used to share a connection between several handlers. + * Specifies a shared handler for a set of APIHandlers. The versionMap, the httpClientHandler and the + * configuration will be shared among them. Its primary purpose is to avoid unnecessary calls to + * '/api/version'. * - * @param sharedConnectionHandler The shared connection handler. + * @param sharedConnectionHandler The handler to share. * @return {@link #self()} + * @see GraphConnectionHandler */ public T setSharedConnectionHandler(AbstractVersionAPIHandler sharedConnectionHandler) { this.sharedConnectionHandler = sharedConnectionHandler; @@ -44,7 +46,6 @@ public T setSharedConnectionHandler(AbstractVersionAPIHandler sharedConnectionHa // ############################################################################################### private VersionResponse versionMap; - private final AbstractVersionAPIHandler sharedConnectionHandler; /** @@ -53,19 +54,8 @@ public T setSharedConnectionHandler(AbstractVersionAPIHandler sharedConnectionHa * @param builder The builder to use. */ protected AbstractVersionAPIHandler(Conf builder) { - super(builder); - this.sharedConnectionHandler = null; - } - - /** - * Protected Copy Constructor. Fields shall be copied from another AbstractVersionAPIHandler. - * - * @param other The source AbstractVersionAPIHandler. - */ - protected AbstractVersionAPIHandler(AbstractVersionAPIHandler other) { - super(other); - this.versionMap = other.versionMap; - this.sharedConnectionHandler = other; + super(builder.getSharedConnectionHandler() == null ? builder : builder.getSharedConnectionHandler().getConf()); + this.sharedConnectionHandler = builder.sharedConnectionHandler; } /** @@ -88,7 +78,7 @@ protected VersionResponse requestVersionMap() throws IOException, InterruptedExc /** * Returns the current {@link #versionMap}. If no {@link #versionMap} is available, it will be requested, cached - * and then returned. + * and then returned. if a {@link #sharedConnectionHandler} is set, the versionMap will be obtained from there. * * @return The (cached) versionMap. * @throws HiroException When the request fails. diff --git a/src/main/java/co/arago/hiro/client/connection/GraphConnectionHandler.java b/src/main/java/co/arago/hiro/client/connection/GraphConnectionHandler.java index 3bf7c3e..23195ad 100644 --- a/src/main/java/co/arago/hiro/client/connection/GraphConnectionHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/GraphConnectionHandler.java @@ -7,13 +7,14 @@ * TokenAPIHandlers, i.e. a FixedTokenApiHandler with a token for each user that shall all use the same connection. * Contains CookieHandler, the versionMap and the httpConnection. */ -public class GraphConnectionHandler extends AbstractVersionAPIHandler { +public class GraphConnectionHandler extends AbstractVersionAPIHandler implements AutoCloseable { // ############################################################################################### // ## Conf and Builder ## // ############################################################################################### public static abstract class Conf> extends AbstractVersionAPIHandler.Conf { + @Override public abstract GraphConnectionHandler build(); } @@ -26,8 +27,7 @@ protected Builder self() { } public GraphConnectionHandler build() { - return (getSharedConnectionHandler() != null) ? new GraphConnectionHandler(getSharedConnectionHandler()) - : new GraphConnectionHandler(this); + return new GraphConnectionHandler(this); } } @@ -45,15 +45,6 @@ protected GraphConnectionHandler(Conf builder) { super(builder); } - /** - * Protected Copy Constructor. Fields shall be copied from another AbstractVersionAPIHandler. - * - * @param other The source AbstractVersionAPIHandler. - */ - protected GraphConnectionHandler(AbstractVersionAPIHandler other) { - super(other); - } - public static Conf newBuilder() { return new Builder(); } @@ -69,4 +60,11 @@ public void addToHeaders(HttpHeaderMap headers) { headers.set("User-Agent", userAgent); } + /** + * Close the underlying httpClientHandler. + */ + @Override + public void close() { + httpClientHandler.close(); + } } diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractClientAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java similarity index 73% rename from src/main/java/co/arago/hiro/client/connection/AbstractClientAPIHandler.java rename to src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java index 6b8502e..bac97b5 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractClientAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java @@ -1,4 +1,4 @@ -package co.arago.hiro.client.connection; +package co.arago.hiro.client.connection.httpclient; import co.arago.hiro.client.util.HttpLogger; import org.slf4j.Logger; @@ -25,9 +25,9 @@ /** * Class for API httpRequests that contains a HttpClient and a HttpLogger. */ -public abstract class AbstractClientAPIHandler extends AbstractAPIHandler implements AutoCloseable { +public class DefaultHttpClientHandler implements HttpClientHandler { - final static Logger log = LoggerFactory.getLogger(AbstractClientAPIHandler.class); + final static Logger log = LoggerFactory.getLogger(DefaultHttpClientHandler.class); protected final static long DEFAULT_SHUTDOWN_TIMEOUT = 3000; protected final static int DEFAULT_MAX_BINARY_LOG_LENGTH = 1024; @@ -37,8 +37,8 @@ public abstract class AbstractClientAPIHandler extends AbstractAPIHandler implem // ## Conf and Builder ## // ############################################################################################### - public static abstract class Conf> extends AbstractAPIHandler.Conf { - private AbstractClientAPIHandler.ProxySpec proxy; + public static abstract class Conf> { + private HttpClientHandler.ProxySpec proxy; private boolean followRedirects = true; private Boolean acceptAllCerts; private Long connectTimeout; @@ -50,16 +50,17 @@ public static abstract class Conf> extends AbstractAPIHandler. private SSLContext sslContext; private int maxConnectionPool = DEFAULT_MAX_CONNECTION_POOL; private int maxBinaryLogLength = DEFAULT_MAX_BINARY_LOG_LENGTH; + private Boolean httpClientAutoClose; - ProxySpec getProxy() { + public ProxySpec getProxy() { return proxy; } /** * @param proxy Simple proxy with one address and port * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setProxy(ProxySpec proxy) { this.proxy = proxy; return self(); @@ -72,8 +73,8 @@ public boolean isFollowRedirects() { /** * @param followRedirects Enable Redirect.NORMAL. Default is true. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; return self(); @@ -86,8 +87,8 @@ public Long getConnectTimeout() { /** * @param connectTimeout Connect timeout in milliseconds. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setConnectTimeout(Long connectTimeout) { this.connectTimeout = connectTimeout; return self(); @@ -102,8 +103,8 @@ public long getShutdownTimeout() { * If this is set to a value too low, you might need to wait elsewhere for the HttpClient * to shut down properly. Default is 3000ms. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setShutdownTimeout(long shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; return self(); @@ -119,8 +120,8 @@ public Boolean getAcceptAllCerts() { * * @param acceptAllCerts the toggle * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setAcceptAllCerts(Boolean acceptAllCerts) { this.acceptAllCerts = acceptAllCerts; return self(); @@ -133,9 +134,9 @@ public SSLContext getSslContext() { /** * @param sslContext The specific SSLContext to use. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. * @see #setAcceptAllCerts(Boolean) */ + public T setSslContext(SSLContext sslContext) { this.sslContext = sslContext; return self(); @@ -148,8 +149,8 @@ public SSLParameters getSslParameters() { /** * @param sslParameters The specific SSLParameters to use. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setSslParameters(SSLParameters sslParameters) { this.sslParameters = sslParameters; return self(); @@ -165,8 +166,11 @@ public HttpClient getHttpClient() { * * @param httpClient Instance of an HttpClient. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. + * @implNote Be aware, that any httpClient given via this method will be marked as external and has to be + * closed externally as well. A call to {@link DefaultHttpClientHandler#close()} with an external httpclient + * will have no effect. */ + public T setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return self(); @@ -182,8 +186,8 @@ public CookieManager getCookieManager() { * * @param cookieManager Instance of a CookieManager. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setCookieManager(CookieManager cookieManager) { this.cookieManager = cookieManager; return self(); @@ -199,8 +203,8 @@ public int getMaxConnectionPool() { * * @param maxConnectionPool Maximum size of the pool. Default is 8. * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setMaxConnectionPool(int maxConnectionPool) { this.maxConnectionPool = maxConnectionPool; return self(); @@ -215,42 +219,55 @@ public int getMaxBinaryLogLength() { * * @param maxBinaryLogLength Size in bytes * @return {@link #self()} - * @implNote Will not be used in the final class when a sharedConnectionHandler is set. */ + public T setMaxBinaryLogLength(int maxBinaryLogLength) { this.maxBinaryLogLength = maxBinaryLogLength; return self(); } - @Override - public abstract AbstractClientAPIHandler build(); - } - - // ############################################################################################### - // ## Inner classes ## - // ############################################################################################### + public Boolean getHttpClientAutoClose() { + return httpClientAutoClose; + } - /** - * A simple data class for a proxy - */ - public static class ProxySpec { - private final String address; - private final int port; + /** + *

+ * Close internal httpClient automatically, even when it has been set externally. + *

+ *

+ * The default is to close the internal httpClient when it has been created internally and to + * not close the internal httpClient when it has been set via {@link #setHttpClient(HttpClient)} + *

+ * + * @param httpClientAutoClose true: enable, false: disable. + * @return {@link #self()} + */ - public ProxySpec(String address, int port) { - this.address = address; - this.port = port; + public T setHttpClientAutoClose(boolean httpClientAutoClose) { + this.httpClientAutoClose = httpClientAutoClose; + return self(); } - public String getAddress() { - return address; + protected abstract T self(); + + public abstract HttpClientHandler build(); + } + + public static final class Builder extends Conf { + + protected Builder self() { + return this; } - public int getPort() { - return port; + public HttpClientHandler build() { + return new DefaultHttpClientHandler(this); } } + // ############################################################################################### + // ## Inner classes ## + // ############################################################################################### + /** * A TrustManager trusting all certificates */ @@ -270,30 +287,27 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) { // ## Main part ## // ############################################################################################### - protected final AbstractClientAPIHandler.ProxySpec proxy; - protected final boolean followRedirects; - protected final Long connectTimeout; - protected final SSLParameters sslParameters; - protected final SSLContext sslContext; - protected final int maxConnectionPool; - protected final CookieManager cookieManager; + private final HttpClientHandler.ProxySpec proxy; + private final boolean followRedirects; + private final Long connectTimeout; + private final SSLParameters sslParameters; + private final SSLContext sslContext; + private final int maxConnectionPool; + private final CookieManager cookieManager; private HttpClient httpClient; - protected final HttpLogger httpLogger; - protected boolean externalConnection; - - protected final Long shutdownTimeout; + private final HttpLogger httpLogger; + private final boolean httpClientAutoClose; - private final AbstractClientAPIHandler sharedConnectionHandler; + private final Long shutdownTimeout; /** * Protected Constructor. Attributes shall be filled via builders. * * @param builder The builder to use. */ - protected AbstractClientAPIHandler(Conf builder) { - super(builder); + protected DefaultHttpClientHandler(Conf builder) { this.proxy = builder.getProxy(); this.followRedirects = builder.isFollowRedirects(); this.connectTimeout = builder.getConnectTimeout(); @@ -302,11 +316,12 @@ protected AbstractClientAPIHandler(Conf builder) { this.sslParameters = builder.getSslParameters(); this.httpClient = builder.getHttpClient(); this.maxConnectionPool = builder.getMaxConnectionPool(); - this.cookieManager = builder.cookieManager != null ? builder.cookieManager : new CookieManager(); + this.cookieManager = builder.getCookieManager() != null ? builder.getCookieManager() : new CookieManager(); this.httpLogger = new HttpLogger(builder.getMaxBinaryLogLength()); - this.externalConnection = (this.httpClient != null); - this.sharedConnectionHandler = null; + boolean externalConnection = (this.httpClient != null); + this.httpClientAutoClose = builder.getHttpClientAutoClose() != null ? builder.getHttpClientAutoClose() + : !externalConnection; if (acceptAllCerts == null) { this.sslContext = builder.getSslContext(); @@ -324,26 +339,8 @@ protected AbstractClientAPIHandler(Conf builder) { } } - /** - * Protected Copy Constructor. Fields shall be copied from another AbstractClientAPIHandler. - * - * @param other The source AbstractClientAPIHandler. - */ - protected AbstractClientAPIHandler(AbstractClientAPIHandler other) { - super(other); - this.proxy = other.proxy; - this.followRedirects = other.followRedirects; - this.connectTimeout = other.connectTimeout; - this.shutdownTimeout = other.shutdownTimeout; - this.sslContext = other.sslContext; - this.sslParameters = other.sslParameters; - this.maxConnectionPool = other.maxConnectionPool; - this.cookieManager = other.cookieManager; - this.httpLogger = other.httpLogger; - // Always set externalClient to true, so a call to close() does not close the external connection. - // External connections have to be closed on their own. - this.externalConnection = true; - this.sharedConnectionHandler = other; + public static DefaultHttpClientHandler.Builder newBuilder() { + return new Builder(); } // ############################################################################################### @@ -361,9 +358,6 @@ public HttpClient getOrBuildClient() { if (httpClient != null) return httpClient; - if (sharedConnectionHandler != null) - return sharedConnectionHandler.getOrBuildClient(); - HttpClient.Builder builder = HttpClient.newBuilder(); if (followRedirects) @@ -396,8 +390,8 @@ public HttpClient getOrBuildClient() { /** *

* Shut the {@link #httpClient} down by shutting down its ExecutorService. This call will be ignored if - * {@link #externalConnection} is set to true (this happens, when {@link #httpClient} has been provided externally - * or this ClientAPIHandler has been created via its connection copy constructor. + * {@link #httpClientAutoClose} is set to false (this happens by default, when {@link #httpClient} has been provided + * externally and autoClose is not set manually). *

*

* Be aware, that there is a shutdown timeout so the Java 11 HttpClient can clean itself up properly. See @@ -407,7 +401,7 @@ public HttpClient getOrBuildClient() { @Override public void close() { try { - if (externalConnection || httpClient == null || httpClient.executor().isEmpty()) + if (!httpClientAutoClose || httpClient == null || httpClient.executor().isEmpty()) return; Executor executor = httpClient.executor().orElse(null); diff --git a/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java b/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java new file mode 100644 index 0000000..fe4381b --- /dev/null +++ b/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java @@ -0,0 +1,57 @@ +package co.arago.hiro.client.connection.httpclient; + +import co.arago.hiro.client.util.HttpLogger; + +import java.net.http.HttpClient; + +/** + * Interface for a class that contains a HttpClient and a HttpLogger. + */ +public interface HttpClientHandler extends AutoCloseable { + + /** + * A simple data class for a proxy + */ + class ProxySpec { + private final String address; + private final int port; + + public ProxySpec(String address, int port) { + this.address = address; + this.port = port; + } + + public String getAddress() { + return address; + } + + public int getPort() { + return port; + } + } + + /** + * Return the internal {@link HttpClient}. + * + * @return The HttpClient + */ + HttpClient getOrBuildClient(); + + /** + * @return The HttpLogger to use with this class. + */ + HttpLogger getHttpLogger(); + + /** + *

+ * Shut the {@link HttpClient} down by shutting down its ExecutorService. + *

+ *

+ * Be aware, that there is a shutdown timeout so the Java 11 HttpClient can clean itself up properly. See + * {@link DefaultHttpClientHandler.Conf#setShutdownTimeout(long)}. + *

+ */ + @Override + void close(); + +} diff --git a/src/main/java/co/arago/hiro/client/connection/token/AbstractRemoteAuthTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/AbstractRemoteAuthTokenAPIHandler.java index 6ad7715..0d68cbd 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/AbstractRemoteAuthTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/AbstractRemoteAuthTokenAPIHandler.java @@ -1,6 +1,5 @@ package co.arago.hiro.client.connection.token; -import co.arago.hiro.client.connection.AbstractVersionAPIHandler; import co.arago.hiro.client.exceptions.AuthenticationTokenException; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.HiroHttpException; @@ -154,6 +153,7 @@ public T setApiPath(String apiPath) { return self(); } + @Override public abstract AbstractRemoteAuthTokenAPIHandler build(); } @@ -258,39 +258,15 @@ protected AbstractRemoteAuthTokenAPIHandler(Conf builder) { } } - /** - * Special Copy Constructor. Uses the connection of another existing AbstractVersionAPIHandler. - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - * @param builder Only configuration specific to this TokenAPIHandler, see {@link Conf}, will - * be copied from the builder. The AbstractVersionAPIHandler overwrites everything else. - */ - protected AbstractRemoteAuthTokenAPIHandler( - AbstractVersionAPIHandler versionAPIHandler, - Conf builder) { - super(versionAPIHandler); - this.clientId = notBlank(builder.getClientId(), "clientId"); - this.clientSecret = builder.getClientSecret(); - this.organization = builder.getOrganization(); - this.organizationId = builder.getOrganizationId(); - this.apiPath = builder.getApiPath(); - - this.tokenInfo.refreshOffset = builder.getRefreshOffset(); - - if (!builder.getForceLogging()) { - configureLogging(); - } - } - private void configureLogging() { try { - httpLogger.addFilter(getURI("token")); - httpLogger.addFilter(getURI("app")); - httpLogger.addFilter(getURI("refresh")); - httpLogger.addFilter(getURI("revoke")); + getHttpLogger().addFilter(getURI("token")); + getHttpLogger().addFilter(getURI("app")); + getHttpLogger().addFilter(getURI("refresh")); + getHttpLogger().addFilter(getURI("revoke")); } catch (IOException | InterruptedException | HiroException e) { log.error("Cannot get apiPath URI. Disable logging of http bodies.", e); - httpLogger.setLogBody(false); + getHttpLogger().setLogBody(false); } } diff --git a/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java index e7255fb..9399f84 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java @@ -12,7 +12,7 @@ import java.time.Instant; import java.util.Base64; -public abstract class AbstractTokenAPIHandler extends AbstractVersionAPIHandler { +public abstract class AbstractTokenAPIHandler extends AbstractVersionAPIHandler implements AutoCloseable { // ############################################################################################### // ## Conf and Builder ## @@ -36,15 +36,6 @@ protected AbstractTokenAPIHandler(Conf builder) { super(builder); } - /** - * Special Copy Constructor. Uses the connection of another existing AbstractVersionAPIHandler. - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - */ - protected AbstractTokenAPIHandler(AbstractVersionAPIHandler versionAPIHandler) { - super(versionAPIHandler); - } - /** * Override this to add authentication tokens. TokenHandlers do not have tokens, so this only returns default * headers. @@ -137,4 +128,11 @@ public static DecodedToken decodeToken(String token) throws HiroException, IOExc */ public abstract Instant expiryInstant(); + /** + * Close the underlying httpClientHandler. + */ + @Override + public void close() { + httpClientHandler.close(); + } } diff --git a/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java index 390a724..049e079 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java @@ -1,6 +1,5 @@ package co.arago.hiro.client.connection.token; -import co.arago.hiro.client.connection.AbstractVersionAPIHandler; import co.arago.hiro.client.exceptions.AuthenticationTokenException; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.HiroHttpException; @@ -21,6 +20,7 @@ import java.util.Map; import static co.arago.util.validation.ValueChecks.notBlank; +import static co.arago.util.validation.ValueChecks.notNull; public class CodeFlowAuthTokenAPIHandler extends AbstractRemoteAuthTokenAPIHandler { @@ -75,6 +75,7 @@ public T setCredentials(String redirectURI, String clientId) { return self(); } + @Override public abstract CodeFlowAuthTokenAPIHandler build(); } @@ -86,8 +87,7 @@ protected Builder self() { } public CodeFlowAuthTokenAPIHandler build() { - return getSharedConnectionHandler() != null ? new CodeFlowAuthTokenAPIHandler(getSharedConnectionHandler(), this) - : new CodeFlowAuthTokenAPIHandler(this); + return new CodeFlowAuthTokenAPIHandler(this); } } @@ -111,21 +111,6 @@ protected CodeFlowAuthTokenAPIHandler(Conf builder) { this.scope = builder.getScope(); } - /** - * Special Copy Constructor. Uses the connection of another existing AbstractVersionAPIHandler. - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - * @param builder Only configuration specific to a CodeFlowAuthTokenAPIHandler, see {@link Conf}, will - * be copied from the builder. The AbstractVersionAPIHandler overwrites everything else. - */ - protected CodeFlowAuthTokenAPIHandler( - AbstractVersionAPIHandler versionAPIHandler, - Conf builder) { - super(versionAPIHandler, builder); - this.redirectURI = notBlank(builder.getRedirectURI(), "redirectURI"); - this.scope = builder.getScope(); - } - public static Conf newBuilder() { return new Builder(); } diff --git a/src/main/java/co/arago/hiro/client/connection/token/EnvironmentTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/EnvironmentTokenAPIHandler.java index c5106df..08be1ba 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/EnvironmentTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/EnvironmentTokenAPIHandler.java @@ -1,6 +1,5 @@ package co.arago.hiro.client.connection.token; -import co.arago.hiro.client.connection.AbstractVersionAPIHandler; import co.arago.hiro.client.exceptions.FixedTokenException; import co.arago.hiro.client.exceptions.HiroException; import org.apache.commons.lang3.StringUtils; @@ -29,6 +28,7 @@ public T setTokenEnv(String tokenEnv) { return self(); } + @Override public abstract EnvironmentTokenAPIHandler build(); } @@ -39,9 +39,9 @@ protected Builder self() { return this; } + @Override public EnvironmentTokenAPIHandler build() { - return getSharedConnectionHandler() != null ? new EnvironmentTokenAPIHandler(getSharedConnectionHandler(), this) - : new EnvironmentTokenAPIHandler(this); + return new EnvironmentTokenAPIHandler(this); } } @@ -64,17 +64,6 @@ protected EnvironmentTokenAPIHandler(Conf builder) { this.tokenEnv = StringUtils.isBlank(builder.getTokenEnv()) ? DEFAULT_ENV : builder.getTokenEnv(); } - /** - * Special Copy Constructor. Uses the connection of another existing AbstractVersionAPIHandler. - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - * @param builder The builder with the configuration data for this specific class. - */ - protected EnvironmentTokenAPIHandler(AbstractVersionAPIHandler versionAPIHandler, Conf builder) { - super(versionAPIHandler); - this.tokenEnv = StringUtils.isBlank(builder.getTokenEnv()) ? DEFAULT_ENV : builder.getTokenEnv(); - } - public static Conf newBuilder() { return new Builder(); } diff --git a/src/main/java/co/arago/hiro/client/connection/token/FixedTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/FixedTokenAPIHandler.java index d6b3e0e..f7085c8 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/FixedTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/FixedTokenAPIHandler.java @@ -1,6 +1,5 @@ package co.arago.hiro.client.connection.token; -import co.arago.hiro.client.connection.AbstractVersionAPIHandler; import co.arago.hiro.client.exceptions.FixedTokenException; import co.arago.hiro.client.exceptions.HiroException; import org.apache.commons.lang3.StringUtils; @@ -44,8 +43,7 @@ protected Builder self() { } public FixedTokenAPIHandler build() { - return getSharedConnectionHandler() != null ? new FixedTokenAPIHandler(getSharedConnectionHandler(), this) - : new FixedTokenAPIHandler(this); + return new FixedTokenAPIHandler(this); } } @@ -66,17 +64,6 @@ protected FixedTokenAPIHandler(Conf builder) { this.token = notNull(builder.getToken(), "token"); } - /** - * Constructor - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - * @param builder The builder to use for all specific data for this class. - */ - protected FixedTokenAPIHandler(AbstractVersionAPIHandler versionAPIHandler, Conf builder) { - super(versionAPIHandler); - this.token = notNull(builder.getToken(), "token"); - } - public static Conf newBuilder() { return new Builder(); } diff --git a/src/main/java/co/arago/hiro/client/connection/token/PasswordAuthTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/PasswordAuthTokenAPIHandler.java index da64425..8f74fce 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/PasswordAuthTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/PasswordAuthTokenAPIHandler.java @@ -1,6 +1,5 @@ package co.arago.hiro.client.connection.token; -import co.arago.hiro.client.connection.AbstractVersionAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.token.PasswordTokenRequest; import co.arago.hiro.client.model.token.TokenResponse; @@ -79,8 +78,7 @@ protected Builder self() { } public PasswordAuthTokenAPIHandler build() { - return getSharedConnectionHandler() != null ? new PasswordAuthTokenAPIHandler(getSharedConnectionHandler(), this) - : new PasswordAuthTokenAPIHandler(this); + return new PasswordAuthTokenAPIHandler(this); } } @@ -98,22 +96,6 @@ protected PasswordAuthTokenAPIHandler(Conf builder) { this.password = notBlank(builder.getPassword(), "password"); } - /** - * Special Copy Constructor. Uses the connection of another existing AbstractVersionAPIHandler. - * - * @param versionAPIHandler The AbstractVersionAPIHandler with the source data. - * @param builder Only configuration specific to a PasswordAuthTokenAPIHandler, see {@link Conf}, will - * be copied from the builder. The AbstractVersionAPIHandler overwrites everything else. - */ - protected PasswordAuthTokenAPIHandler( - AbstractVersionAPIHandler versionAPIHandler, - Conf builder) { - super(versionAPIHandler, builder); - notBlank(clientSecret, "clientSecret"); - this.username = notBlank(builder.getUsername(), "username"); - this.password = notBlank(builder.getPassword(), "password"); - } - public static Conf newBuilder() { return new Builder(); } diff --git a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java index f449229..fbfbdeb 100644 --- a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java @@ -7,7 +7,6 @@ import co.arago.hiro.client.exceptions.HiroHttpException; import co.arago.hiro.client.exceptions.TokenUnauthorizedException; import co.arago.hiro.client.model.JsonMessage; -import co.arago.hiro.client.util.HttpLogger; import co.arago.hiro.client.util.httpclient.HttpHeaderMap; import co.arago.hiro.client.util.httpclient.StreamContainer; import co.arago.hiro.client.util.httpclient.URIEncodedData; @@ -21,7 +20,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpResponse; import java.nio.charset.Charset; import java.util.Map; @@ -31,6 +29,9 @@ /** * This class is the basis of all authenticated API handlers that make use of the different sections of the HIRO API. + * It copies its configuration from the supplied {@link AbstractTokenAPIHandler} and overrides + * {@link AbstractAPIHandler#addToHeaders(HttpHeaderMap)} and {@link AbstractAPIHandler#checkResponse(HttpResponse, int)} + * to handle tokens. */ public abstract class AuthenticatedAPIHandler extends AbstractAPIHandler { @@ -340,15 +341,16 @@ public T setHttpHeaders(HttpHeaderMap headers) { protected URI apiURI; /** - * Create this APIHandler by using its Builder. + * Create this APIHandler by using its Builder. The API configuration will be copied over from the + * builder.tokenAPIHandler. * * @param builder The builder to use. */ protected AuthenticatedAPIHandler(Conf builder) { - super(notNull(builder.getTokenApiHandler(), "tokenApiHandler")); + super(notNull(builder.tokenAPIHandler, "tokenApiHandler").getConf()); + this.tokenAPIHandler = builder.getTokenApiHandler(); this.apiName = builder.getApiName(); this.apiPath = builder.getApiPath(); - this.tokenAPIHandler = builder.getTokenApiHandler(); if (StringUtils.isAllBlank(this.apiName, this.apiPath)) anyError("Either 'apiName' or 'apiPath' have to be set."); @@ -371,9 +373,9 @@ public URI getEndpointURI(URIPath path, URIEncodedData query, String fragment) if (apiURI == null) apiURI = (apiPath != null ? buildApiURI(apiPath) : tokenAPIHandler.getApiURIOf(apiName)); - URI pathURI = buildURI(apiURI, path.build(), false); + URI pathURI = AbstractAPIHandler.buildURI(apiURI, path.build(), false); - return addQueryFragmentAndNormalize(pathURI, query, fragment); + return AbstractAPIHandler.addQueryFragmentAndNormalize(pathURI, query, fragment); } /** @@ -383,13 +385,13 @@ public URI getEndpointURI(URIPath path, URIEncodedData query, String fragment) */ @Override public void addToHeaders(HttpHeaderMap headers) throws InterruptedException, IOException, HiroException { - headers.set("User-Agent", userAgent); + tokenAPIHandler.addToHeaders(headers); headers.set("Authorization", "Bearer " + tokenAPIHandler.getToken()); } /** * Checks for {@link TokenUnauthorizedException} and {@link HiroHttpException} - * until {@link #maxRetries} is exhausted. + * until {@link AbstractAPIHandler#getMaxRetries()} is exhausted. * Also tries to refresh the token on {@link TokenUnauthorizedException}. * * @param httpResponse The httpResponse from the HttpRequest @@ -403,7 +405,7 @@ public void addToHeaders(HttpHeaderMap headers) throws InterruptedException, IOE public boolean checkResponse(HttpResponse httpResponse, int retryCount) throws HiroException, IOException, InterruptedException { try { - return super.checkResponse(httpResponse, retryCount); + return tokenAPIHandler.checkResponse(httpResponse, retryCount); } catch (TokenUnauthorizedException e) { // Add one additional retry for obtaining a new token. if (retryCount >= 0) { @@ -427,23 +429,4 @@ public boolean checkResponse(HttpResponse httpResponse, int retryCo } } - /** - * Redirect the HttpLogger to the one provided in {@link #tokenAPIHandler}. - * - * @return The HttpLogger to use with this class. - */ - @Override - protected HttpLogger getHttpLogger() { - return tokenAPIHandler.getHttpLogger(); - } - - /** - * Redirect the HttpClient to the one provided in {@link #tokenAPIHandler}. - * - * @return The HttpClient to use with this class. - */ - @Override - protected HttpClient getOrBuildClient() { - return tokenAPIHandler.getOrBuildClient(); - } } diff --git a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java index 6ee9d73..872cabf 100644 --- a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java @@ -1,6 +1,8 @@ package co.arago.hiro.client.connection; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; +import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; @@ -40,12 +42,16 @@ static void init() throws IOException { GenericAPITest.class.getClassLoader().getResourceAsStream("config.json"), ConfigModel.class); + HttpClientHandler httpClientHandler = DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build(); + handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(httpClientHandler) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .build(); apiHandler = GraphAPI.newBuilder(handler).build(); diff --git a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java index 086be5d..f66f2b6 100644 --- a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java @@ -1,6 +1,7 @@ package co.arago.hiro.client.connection.token; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; import co.arago.hiro.client.exceptions.TokenUnauthorizedException; import co.arago.hiro.client.mock.MockGraphitServerExtension; import co.arago.hiro.client.rest.AuthAPI; @@ -13,7 +14,6 @@ import java.io.IOException; import java.net.ConnectException; -import java.net.MalformedURLException; import java.net.URISyntaxException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -38,11 +38,13 @@ void wrongCredentials() { return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .setPassword("Wrong") .build()) { @@ -66,11 +68,13 @@ void wrongClient() { return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .setClientSecret("Wrong") .build()) { @@ -89,15 +93,17 @@ void wrongClient() { } @Test - void wrongUrl() throws MalformedURLException { + void wrongUrl() { if (config == null) return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .setRootApiURI("http://nothing.here") .build()) { diff --git a/src/test/java/co/arago/hiro/client/rest/AuthAPIHandlerTest.java b/src/test/java/co/arago/hiro/client/rest/AuthAPIHandlerTest.java index 0d7a038..6bd6dbb 100644 --- a/src/test/java/co/arago/hiro/client/rest/AuthAPIHandlerTest.java +++ b/src/test/java/co/arago/hiro/client/rest/AuthAPIHandlerTest.java @@ -1,6 +1,8 @@ package co.arago.hiro.client.rest; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; +import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.util.httpclient.HttpResponseParser; @@ -17,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; -import java.nio.file.Paths; import java.util.Base64; @Disabled @@ -34,12 +35,16 @@ static void init() throws IOException { AuthAPIHandlerTest.class.getClassLoader().getResourceAsStream("config.json"), ConfigModel.class); + HttpClientHandler httpClientHandler = DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build(); + handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(httpClientHandler) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .build(); authAPI = AuthAPI.newBuilder(handler) diff --git a/src/test/java/co/arago/hiro/client/rest/GraphAPITest.java b/src/test/java/co/arago/hiro/client/rest/GraphAPITest.java index 1210ecf..07bb2a3 100644 --- a/src/test/java/co/arago/hiro/client/rest/GraphAPITest.java +++ b/src/test/java/co/arago/hiro/client/rest/GraphAPITest.java @@ -1,6 +1,8 @@ package co.arago.hiro.client.rest; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; +import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.vertex.HiroVertexListMessage; @@ -15,7 +17,6 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.nio.file.Paths; import java.util.Map; @Disabled @@ -32,12 +33,16 @@ static void init() throws IOException { GraphAPI.class.getClassLoader().getResourceAsStream("config.json"), ConfigModel.class); + HttpClientHandler httpClientHandler = DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build(); + handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(httpClientHandler) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .build(); graphAPI = GraphAPI.newBuilder(handler).build(); diff --git a/src/test/java/co/arago/hiro/client/rest/TokenAPIHandlerTest.java b/src/test/java/co/arago/hiro/client/rest/TokenAPIHandlerTest.java index 53fd384..5fab0a8 100644 --- a/src/test/java/co/arago/hiro/client/rest/TokenAPIHandlerTest.java +++ b/src/test/java/co/arago/hiro/client/rest/TokenAPIHandlerTest.java @@ -1,6 +1,8 @@ package co.arago.hiro.client.rest; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; +import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.util.json.JsonUtil; @@ -14,7 +16,6 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -32,12 +33,16 @@ static void init() throws IOException { TokenAPIHandlerTest.class.getClassLoader().getResourceAsStream("config.json"), ConfigModel.class); + HttpClientHandler httpClientHandler = DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build(); + handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(httpClientHandler) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .build(); } catch (URISyntaxException e) { log.warn("Skipping tests: {}.", e.getMessage()); diff --git a/src/test/java/co/arago/hiro/client/websocket/EventWebSocketTest.java b/src/test/java/co/arago/hiro/client/websocket/EventWebSocketTest.java index 1658655..c4487f4 100644 --- a/src/test/java/co/arago/hiro/client/websocket/EventWebSocketTest.java +++ b/src/test/java/co/arago/hiro/client/websocket/EventWebSocketTest.java @@ -1,6 +1,7 @@ package co.arago.hiro.client.websocket; import co.arago.hiro.client.ConfigModel; +import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; import co.arago.hiro.client.connection.token.FixedTokenAPIHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; @@ -69,11 +70,13 @@ void testEventWebsocket() throws InterruptedException, IOException, HiroExceptio return; try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) - .setAcceptAllCerts(config.accept_all_certs) .setForceLogging(config.force_logging) - .setShutdownTimeout(0) .build()) { DecodedToken decodedToken = handler.decodeToken(); @@ -101,10 +104,12 @@ void testInvalidToken() throws InterruptedException, IOException, HiroException EventListener listener = new EventListener(); try (FixedTokenAPIHandler handler = FixedTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setRootApiURI(config.api_url) .setToken("Invalid") - .setAcceptAllCerts(config.accept_all_certs) - .setShutdownTimeout(0) .build()) { try (EventWebSocket eventWebSocket = EventWebSocket.newBuilder(handler, listener) .setName("events-ws-test") @@ -124,17 +129,19 @@ void testInvalidToken() throws InterruptedException, IOException, HiroException } @Test - void testInvalidUrl() throws IOException { + void testInvalidUrl() { if (config == null) return; EventListener listener = new EventListener(); try (FixedTokenAPIHandler handler = FixedTokenAPIHandler.newBuilder() + .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) + .build()) .setRootApiURI("http://nothing.here") .setToken("Invalid") - .setAcceptAllCerts(config.accept_all_certs) - .setShutdownTimeout(0) .build()) { try (EventWebSocket eventWebSocket = EventWebSocket.newBuilder(handler, listener) From 929f46bfe3515f7a8e1f7bfc91cb17fa4fc6e77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 11:03:38 +0200 Subject: [PATCH 2/8] Refactorings * Integrate HttpClientHandler.ConfTemplate into AbstractAPIHandler.Conf so the HttpClientHandler does not have to be configured externally. * Do not ignore FixedTokenException. Refactorings. * Documentation. --- README.md | 12 +- .../client/connection/AbstractAPIHandler.java | 242 +++++++++++++++++- .../httpclient/DefaultHttpClientHandler.java | 55 +++- .../httpclient/HttpClientHandler.java | 166 ++++++++++-- .../token/CodeFlowAuthTokenAPIHandler.java | 1 - .../exceptions/FixedTokenException.java | 20 ++ .../client/rest/AuthenticatedAPIHandler.java | 38 +-- .../client/connection/GenericAPITest.java | 8 +- .../token/InvalidCredentialsAPITest.java | 19 +- 9 files changed, 489 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 849427c..9d69e88 100644 --- a/README.md +++ b/README.md @@ -265,16 +265,12 @@ class Example { .executor(Executors.newFixedThreadPool(1)) .build(); - // Build an API handler which takes care of API paths via /api/versions and security tokens. - // Use external httpClientHandler. + // Pass the external httpClient and set httpClientAutoClose, so it gets closed + // when the TokenAPIHandler closes. try (PasswordAuthTokenAPIHandler handler = PasswordAuthTokenAPIHandler .newBuilder() - .setHttpClientHandler( - DefaultHttpClientHandler.newBuilder() - .setHttpClient(httpClient) - .setHttpClientAutoClose(true) // If this is missing, the httpClient will not be closed. - .build() - ) + .setHttpClient(httpClient) + .setHttpClientAutoClose(true) // If this is missing, the httpClient will not be closed. .setRootApiUri(API_URL) .setCredentials(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET) .build()) { diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java index 85c14a3..79ff8f1 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java @@ -18,8 +18,11 @@ import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import java.io.IOException; import java.io.InputStream; +import java.net.CookieManager; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; @@ -42,18 +45,24 @@ public abstract class AbstractAPIHandler { // ############################################################################################### /** - * The basic configuration for all APIHAndler + * The basic configuration for all APIHAndler. This also integrates the configuration for a default + * HttpClientHandler. * * @param The type of the Builder */ - public static abstract class Conf> { + public static abstract class Conf> implements HttpClientHandler.ConfTemplate { private URI rootApiURI; private URI webSocketURI; private String userAgent; private Long httpRequestTimeout; private int maxRetries; + + // Reference to an already existing httpClientHandler. private HttpClientHandler httpClientHandler; + // Configuration for a DefaultHttpClientHandler that is created internally. + private final DefaultHttpClientHandler.Conf defaultHttpClientHandlerBuilder = DefaultHttpClientHandler.newBuilder(); + public URI getRootApiURI() { return rootApiURI; } @@ -147,17 +156,244 @@ public HttpClientHandler getHttpClientHandler() { } /** + *

* Sets the httpClientHandler for the backend connection. This handler will be shared among all * APIHandlers that use this configuration instance. + *

+ *

+ * Setting this handler means, that no DefaultHttpClientHandler will be created internally. All configuration + * options that are used for the internal handler will be ignored. + *

* * @param httpClientHandler The connection handler to use. * @return {@link #self()} + * @see HttpClientHandler.ConfTemplate */ public T setHttpClientHandler(HttpClientHandler httpClientHandler) { this.httpClientHandler = httpClientHandler; return self(); } + @Override + public HttpClientHandler.ProxySpec getProxy() { + return defaultHttpClientHandlerBuilder.getProxy(); + } + + /** + * @param proxy Simple proxy with one address and port + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setProxy(HttpClientHandler.ProxySpec proxy) { + defaultHttpClientHandlerBuilder.setProxy(proxy); + return self(); + } + + @Override + public boolean isFollowRedirects() { + return defaultHttpClientHandlerBuilder.isFollowRedirects(); + } + + /** + * @param followRedirects Enable Redirect.NORMAL. Default is true. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setFollowRedirects(boolean followRedirects) { + defaultHttpClientHandlerBuilder.setFollowRedirects(followRedirects); + return self(); + } + + @Override + public Long getConnectTimeout() { + return defaultHttpClientHandlerBuilder.getShutdownTimeout(); + } + + /** + * @param connectTimeout Connect timeout in milliseconds. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setConnectTimeout(Long connectTimeout) { + defaultHttpClientHandlerBuilder.setConnectTimeout(connectTimeout); + return self(); + } + + @Override + public long getShutdownTimeout() { + return defaultHttpClientHandlerBuilder.getShutdownTimeout(); + } + + /** + * @param shutdownTimeout Time to wait in milliseconds for a complete shutdown of the Java 11 HttpClientImpl. + * If this is set to a value too low, you might need to wait elsewhere for the HttpClient + * to shut down properly. Default is 3000ms. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setShutdownTimeout(long shutdownTimeout) { + defaultHttpClientHandlerBuilder.setShutdownTimeout(shutdownTimeout); + return self(); + } + + @Override + public Boolean getAcceptAllCerts() { + return defaultHttpClientHandlerBuilder.getAcceptAllCerts(); + } + + /** + * Skip SSL certificate verification. Leave this unset to use the default in HttpClient. Setting this to true + * installs a permissive SSLContext, setting it to false removes the SSLContext to use the default. + * + * @param acceptAllCerts the toggle + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setAcceptAllCerts(Boolean acceptAllCerts) { + defaultHttpClientHandlerBuilder.setAcceptAllCerts(acceptAllCerts); + return self(); + } + + @Override + public SSLContext getSslContext() { + return defaultHttpClientHandlerBuilder.getSslContext(); + } + + /** + * @param sslContext The specific SSLContext to use. + * @return {@link #self()} + * @see #setAcceptAllCerts(Boolean) + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setSslContext(SSLContext sslContext) { + defaultHttpClientHandlerBuilder.setSslContext(sslContext); + return self(); + } + + @Override + public SSLParameters getSslParameters() { + return defaultHttpClientHandlerBuilder.getSslParameters(); + } + + /** + * @param sslParameters The specific SSLParameters to use. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setSslParameters(SSLParameters sslParameters) { + defaultHttpClientHandlerBuilder.setSslParameters(sslParameters); + return self(); + } + + @Override + public HttpClient getHttpClient() { + return defaultHttpClientHandlerBuilder.getHttpClient(); + } + + /** + * Instance of an externally configured http client. An internal HttpClient will be built with parameters + * given by this Builder if this is not set. + * + * @param httpClient Instance of an HttpClient. + * @return {@link #self()} + * @implNote Be aware, that any httpClient given via this method will be marked as external and has to be + * closed externally as well. A call to {@link DefaultHttpClientHandler#close()} with an external httpclient + * will have no effect. + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setHttpClient(HttpClient httpClient) { + defaultHttpClientHandlerBuilder.setHttpClient(httpClient); + return self(); + } + + @Override + public CookieManager getCookieManager() { + return defaultHttpClientHandlerBuilder.getCookieManager(); + } + + /** + * Instance of an externally configured CookieManager. An internal CookieManager will be built if this is not + * set. + * + * @param cookieManager Instance of a CookieManager. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setCookieManager(CookieManager cookieManager) { + defaultHttpClientHandlerBuilder.setCookieManager(cookieManager); + return self(); + } + + @Override + public int getMaxConnectionPool() { + return defaultHttpClientHandlerBuilder.getMaxConnectionPool(); + } + + /** + * Set the maximum of open connections for this HttpClient (This sets the fixedThreadPool for the + * Executor of the HttpClient). + * + * @param maxConnectionPool Maximum size of the pool. Default is 8. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setMaxConnectionPool(int maxConnectionPool) { + defaultHttpClientHandlerBuilder.setMaxConnectionPool(maxConnectionPool); + return self(); + } + + @Override + public int getMaxBinaryLogLength() { + return defaultHttpClientHandlerBuilder.getMaxBinaryLogLength(); + } + + /** + * Maximum size to log binary data in logfiles. Default is 1024. + * + * @param maxBinaryLogLength Size in bytes + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setMaxBinaryLogLength(int maxBinaryLogLength) { + defaultHttpClientHandlerBuilder.setMaxBinaryLogLength(maxBinaryLogLength); + return self(); + } + + @Override + public Boolean getHttpClientAutoClose() { + return defaultHttpClientHandlerBuilder.getHttpClientAutoClose(); + } + + /** + *

+ * Close internal httpClient automatically, even when it has been set externally. + *

+ *

+ * The default is to close the internal httpClient when it has been created internally and to + * not close the internal httpClient when it has been set via {@link #setHttpClient(HttpClient)} + *

+ * + * @param httpClientAutoClose true: enable, false: disable. + * @return {@link #self()} + * @implNote Will be ignored if {@link #httpClientHandler} is set. + */ + @Override + public T setHttpClientAutoClose(boolean httpClientAutoClose) { + defaultHttpClientHandlerBuilder.setHttpClientAutoClose(httpClientAutoClose); + return self(); + } + protected abstract T self(); public abstract AbstractAPIHandler build(); @@ -210,7 +446,7 @@ protected AbstractAPIHandler(Conf builder) { : (version != null ? title + " " + version : title); this.httpClientHandler = builder.getHttpClientHandler() != null ? builder.getHttpClientHandler() - : DefaultHttpClientHandler.newBuilder().build(); + : builder.defaultHttpClientHandlerBuilder.build(); } /** diff --git a/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java b/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java index bac97b5..59d537e 100644 --- a/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/httpclient/DefaultHttpClientHandler.java @@ -23,7 +23,8 @@ import java.util.concurrent.TimeUnit; /** - * Class for API httpRequests that contains a HttpClient and a HttpLogger. + * Class for API httpRequests that contains a HttpClient and a HttpLogger. Wraps around the HttpClient for easier + * handling on cleanup and using common default parameters. */ public class DefaultHttpClientHandler implements HttpClientHandler { @@ -37,7 +38,7 @@ public class DefaultHttpClientHandler implements HttpClientHandler { // ## Conf and Builder ## // ############################################################################################### - public static abstract class Conf> { + public static abstract class Conf> implements ConfTemplate { private HttpClientHandler.ProxySpec proxy; private boolean followRedirects = true; private Boolean acceptAllCerts; @@ -52,6 +53,7 @@ public static abstract class Conf> { private int maxBinaryLogLength = DEFAULT_MAX_BINARY_LOG_LENGTH; private Boolean httpClientAutoClose; + @Override public ProxySpec getProxy() { return proxy; } @@ -61,11 +63,13 @@ public ProxySpec getProxy() { * @return {@link #self()} */ + @Override public T setProxy(ProxySpec proxy) { this.proxy = proxy; return self(); } + @Override public boolean isFollowRedirects() { return followRedirects; } @@ -75,11 +79,13 @@ public boolean isFollowRedirects() { * @return {@link #self()} */ + @Override public T setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; return self(); } + @Override public Long getConnectTimeout() { return connectTimeout; } @@ -89,11 +95,13 @@ public Long getConnectTimeout() { * @return {@link #self()} */ + @Override public T setConnectTimeout(Long connectTimeout) { this.connectTimeout = connectTimeout; return self(); } + @Override public long getShutdownTimeout() { return shutdownTimeout; } @@ -105,11 +113,13 @@ public long getShutdownTimeout() { * @return {@link #self()} */ + @Override public T setShutdownTimeout(long shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; return self(); } + @Override public Boolean getAcceptAllCerts() { return acceptAllCerts; } @@ -122,11 +132,13 @@ public Boolean getAcceptAllCerts() { * @return {@link #self()} */ + @Override public T setAcceptAllCerts(Boolean acceptAllCerts) { this.acceptAllCerts = acceptAllCerts; return self(); } + @Override public SSLContext getSslContext() { return sslContext; } @@ -137,11 +149,13 @@ public SSLContext getSslContext() { * @see #setAcceptAllCerts(Boolean) */ + @Override public T setSslContext(SSLContext sslContext) { this.sslContext = sslContext; return self(); } + @Override public SSLParameters getSslParameters() { return sslParameters; } @@ -151,11 +165,13 @@ public SSLParameters getSslParameters() { * @return {@link #self()} */ + @Override public T setSslParameters(SSLParameters sslParameters) { this.sslParameters = sslParameters; return self(); } + @Override public HttpClient getHttpClient() { return httpClient; } @@ -171,11 +187,13 @@ public HttpClient getHttpClient() { * will have no effect. */ + @Override public T setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return self(); } + @Override public CookieManager getCookieManager() { return cookieManager; } @@ -188,11 +206,13 @@ public CookieManager getCookieManager() { * @return {@link #self()} */ + @Override public T setCookieManager(CookieManager cookieManager) { this.cookieManager = cookieManager; return self(); } + @Override public int getMaxConnectionPool() { return maxConnectionPool; } @@ -205,11 +225,13 @@ public int getMaxConnectionPool() { * @return {@link #self()} */ + @Override public T setMaxConnectionPool(int maxConnectionPool) { this.maxConnectionPool = maxConnectionPool; return self(); } + @Override public int getMaxBinaryLogLength() { return maxBinaryLogLength; } @@ -221,11 +243,13 @@ public int getMaxBinaryLogLength() { * @return {@link #self()} */ + @Override public T setMaxBinaryLogLength(int maxBinaryLogLength) { this.maxBinaryLogLength = maxBinaryLogLength; return self(); } + @Override public Boolean getHttpClientAutoClose() { return httpClientAutoClose; } @@ -243,6 +267,7 @@ public Boolean getHttpClientAutoClose() { * @return {@link #self()} */ + @Override public T setHttpClientAutoClose(boolean httpClientAutoClose) { this.httpClientAutoClose = httpClientAutoClose; return self(); @@ -283,6 +308,27 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; + /** + * A simple data class for a proxy + */ + public static class DefaultProxySpec implements ProxySpec { + private final String address; + private final int port; + + public DefaultProxySpec(String address, int port) { + this.address = address; + this.port = port; + } + + public String getAddress() { + return address; + } + + public int getPort() { + return port; + } + } + // ############################################################################################### // ## Main part ## // ############################################################################################### @@ -319,9 +365,8 @@ protected DefaultHttpClientHandler(Conf builder) { this.cookieManager = builder.getCookieManager() != null ? builder.getCookieManager() : new CookieManager(); this.httpLogger = new HttpLogger(builder.getMaxBinaryLogLength()); - boolean externalConnection = (this.httpClient != null); - this.httpClientAutoClose = builder.getHttpClientAutoClose() != null ? builder.getHttpClientAutoClose() - : !externalConnection; + this.httpClientAutoClose = (builder.getHttpClientAutoClose() != null) ? builder.getHttpClientAutoClose() + : (this.httpClient == null); if (acceptAllCerts == null) { this.sslContext = builder.getSslContext(); diff --git a/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java b/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java index fe4381b..77b97ef 100644 --- a/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/httpclient/HttpClientHandler.java @@ -2,6 +2,9 @@ import co.arago.hiro.client.util.HttpLogger; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import java.net.CookieManager; import java.net.http.HttpClient; /** @@ -10,24 +13,153 @@ public interface HttpClientHandler extends AutoCloseable { /** - * A simple data class for a proxy + * Interface for the configuration parameters of a HttpClientHandler. + * + * @param Type of the Builder using this configuration. */ - class ProxySpec { - private final String address; - private final int port; - - public ProxySpec(String address, int port) { - this.address = address; - this.port = port; - } - - public String getAddress() { - return address; - } - - public int getPort() { - return port; - } + interface ConfTemplate> { + ProxySpec getProxy(); + + /** + * @param proxy Simple proxy with one address and port + * @return self() + */ + + T setProxy(ProxySpec proxy); + + boolean isFollowRedirects(); + + /** + * @param followRedirects Enable Redirect.NORMAL. Default is true. + * @return self() + */ + + T setFollowRedirects(boolean followRedirects); + + Long getConnectTimeout(); + + /** + * @param connectTimeout Connect timeout in milliseconds. + * @return self() + */ + + T setConnectTimeout(Long connectTimeout); + + long getShutdownTimeout(); + + /** + * @param shutdownTimeout Time to wait in milliseconds for a complete shutdown of the Java 11 HttpClientImpl. + * If this is set to a value too low, you might need to wait elsewhere for the HttpClient + * to shut down properly. Default is 3000ms. + * @return self() + */ + + T setShutdownTimeout(long shutdownTimeout); + + Boolean getAcceptAllCerts(); + + /** + * Skip SSL certificate verification. Leave this unset to use the default in HttpClient. Setting this to true + * installs a permissive SSLContext, setting it to false removes the SSLContext to use the default. + * + * @param acceptAllCerts the toggle + * @return self() + */ + + T setAcceptAllCerts(Boolean acceptAllCerts); + + SSLContext getSslContext(); + + /** + * @param sslContext The specific SSLContext to use. + * @return self() + * @see #setAcceptAllCerts(Boolean) + */ + + T setSslContext(SSLContext sslContext); + + SSLParameters getSslParameters(); + + /** + * @param sslParameters The specific SSLParameters to use. + * @return self() + */ + + T setSslParameters(SSLParameters sslParameters); + + HttpClient getHttpClient(); + + /** + * Instance of an externally configured http client. An internal HttpClient will be built with parameters + * given by this Builder if this is not set. + * + * @param httpClient Instance of an HttpClient. + * @return self() + * @implNote Be aware, that any httpClient given via this method will be marked as external and has to be + * closed externally as well. A call to {@link DefaultHttpClientHandler#close()} with an external httpclient + * will have no effect. + */ + + T setHttpClient(HttpClient httpClient); + + CookieManager getCookieManager(); + + /** + * Instance of an externally configured CookieManager. An internal CookieManager will be built if this is not + * set. + * + * @param cookieManager Instance of a CookieManager. + * @return self() + */ + + T setCookieManager(CookieManager cookieManager); + + int getMaxConnectionPool(); + + /** + * Set the maximum of open connections for this HttpClient (This sets the fixedThreadPool for the + * Executor of the HttpClient). + * + * @param maxConnectionPool Maximum size of the pool. Default is 8. + * @return self() + */ + + T setMaxConnectionPool(int maxConnectionPool); + + int getMaxBinaryLogLength(); + + /** + * Maximum size to log binary data in logfiles. Default is 1024. + * + * @param maxBinaryLogLength Size in bytes + * @return self() + */ + + T setMaxBinaryLogLength(int maxBinaryLogLength); + + Boolean getHttpClientAutoClose(); + + /** + *

+ * Close internal httpClient automatically, even when it has been set externally. + *

+ *

+ * The default is to close the internal httpClient when it has been created internally and to + * not close the internal httpClient when it has been set via {@link #setHttpClient(HttpClient)} + *

+ * + * @param httpClientAutoClose true: enable, false: disable. + * @return self() + */ + + T setHttpClientAutoClose(boolean httpClientAutoClose); + + } + + interface ProxySpec { + String getAddress(); + + int getPort(); } /** diff --git a/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java index 049e079..b237674 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/CodeFlowAuthTokenAPIHandler.java @@ -20,7 +20,6 @@ import java.util.Map; import static co.arago.util.validation.ValueChecks.notBlank; -import static co.arago.util.validation.ValueChecks.notNull; public class CodeFlowAuthTokenAPIHandler extends AbstractRemoteAuthTokenAPIHandler { diff --git a/src/main/java/co/arago/hiro/client/exceptions/FixedTokenException.java b/src/main/java/co/arago/hiro/client/exceptions/FixedTokenException.java index 71bd91a..ea391e2 100644 --- a/src/main/java/co/arago/hiro/client/exceptions/FixedTokenException.java +++ b/src/main/java/co/arago/hiro/client/exceptions/FixedTokenException.java @@ -18,4 +18,24 @@ public class FixedTokenException extends HiroException { public FixedTokenException(String message) { super(message); } + + /** + * Constructs a new exception with the specified detail message and + * cause. + *

+ * Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public FixedTokenException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java index fbfbdeb..f3ac6b1 100644 --- a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java @@ -395,11 +395,14 @@ public void addToHeaders(HttpHeaderMap headers) throws InterruptedException, IOE * Also tries to refresh the token on {@link TokenUnauthorizedException}. * * @param httpResponse The httpResponse from the HttpRequest - * @param retryCount current counter for retries + * @param retryCount current counter for retries. Counts down to zero. * @return true for a retry, false otherwise. - * @throws HiroException If the check fails with a http status code error. - * @throws IOException When the refresh fails with an IO error. - * @throws InterruptedException Call got interrupted. + * @throws TokenUnauthorizedException When the statusCode is 401 and all retries are exhausted or the token cannot + * be refreshed. + * @throws HiroHttpException When the statusCode is < 200 or > 399 and all retries are exhausted. + * @throws HiroException On internal errors regarding hiro data processing. + * @throws IOException When the refresh fails with an IO error. + * @throws InterruptedException Call got interrupted. */ @Override public boolean checkResponse(HttpResponse httpResponse, int retryCount) @@ -407,25 +410,24 @@ public boolean checkResponse(HttpResponse httpResponse, int retryCo try { return tokenAPIHandler.checkResponse(httpResponse, retryCount); } catch (TokenUnauthorizedException e) { - // Add one additional retry for obtaining a new token. - if (retryCount >= 0) { - log.info("Trying to refresh token because of {}.", e.toString()); - try { - tokenAPIHandler.refreshToken(); - } catch (FixedTokenException ignored) { + try { + if (retryCount < 0) throw e; - } + + log.info("Trying to refresh token because of {}.", e.toString()); + tokenAPIHandler.refreshToken(); return true; - } else { - throw e; + } catch (FixedTokenException fte) { + // Integrate the FixedTokenException into the exception chain while keeping the original type. + throw new TokenUnauthorizedException(e.getMessage() + " " + fte.getMessage(), e.getCode(), e.getBody(), + new FixedTokenException(fte.getMessage(), e)); } } catch (HiroHttpException e) { - if (retryCount > 0) { - log.info("Retrying with {} retries left because of {}", retryCount, e.toString()); - return true; - } else { + if (retryCount <= 0) throw e; - } + + log.info("Retrying with {} retries left because of {}", retryCount, e.toString()); + return true; } } diff --git a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java index 872cabf..9772322 100644 --- a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java @@ -1,8 +1,6 @@ package co.arago.hiro.client.connection; import co.arago.hiro.client.ConfigModel; -import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; -import co.arago.hiro.client.connection.httpclient.HttpClientHandler; import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; @@ -42,13 +40,9 @@ static void init() throws IOException { GenericAPITest.class.getClassLoader().getResourceAsStream("config.json"), ConfigModel.class); - HttpClientHandler httpClientHandler = DefaultHttpClientHandler.newBuilder() + handler = PasswordAuthTokenAPIHandler.newBuilder() .setAcceptAllCerts(config.accept_all_certs) .setShutdownTimeout(0) - .build(); - - handler = PasswordAuthTokenAPIHandler.newBuilder() - .setHttpClientHandler(httpClientHandler) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) .setForceLogging(config.force_logging) diff --git a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java index f66f2b6..caa1d52 100644 --- a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java @@ -1,7 +1,6 @@ package co.arago.hiro.client.connection.token; import co.arago.hiro.client.ConfigModel; -import co.arago.hiro.client.connection.httpclient.DefaultHttpClientHandler; import co.arago.hiro.client.exceptions.TokenUnauthorizedException; import co.arago.hiro.client.mock.MockGraphitServerExtension; import co.arago.hiro.client.rest.AuthAPI; @@ -38,10 +37,8 @@ void wrongCredentials() { return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() - .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() - .setAcceptAllCerts(config.accept_all_certs) - .setShutdownTimeout(0) - .build()) + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) .setForceLogging(config.force_logging) @@ -68,10 +65,8 @@ void wrongClient() { return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() - .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() - .setAcceptAllCerts(config.accept_all_certs) - .setShutdownTimeout(0) - .build()) + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) .setRootApiURI(config.api_url) .setCredentials(config.username, config.password, config.client_id, config.client_secret) .setForceLogging(config.force_logging) @@ -98,10 +93,8 @@ void wrongUrl() { return; try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() - .setHttpClientHandler(DefaultHttpClientHandler.newBuilder() - .setAcceptAllCerts(config.accept_all_certs) - .setShutdownTimeout(0) - .build()) + .setAcceptAllCerts(config.accept_all_certs) + .setShutdownTimeout(0) .setCredentials(config.username, config.password, config.client_id, config.client_secret) .setForceLogging(config.force_logging) .setRootApiURI("http://nothing.here") From 5e6cd12144b87f88bccb99e1e2b719d39aa275ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 12:36:29 +0200 Subject: [PATCH 3/8] Create interface TokenAPIHandler --- README.md | 2 +- .../client/connection/AbstractAPIHandler.java | 4 +- .../token/AbstractTokenAPIHandler.java | 79 +------- .../connection/token/TokenAPIHandler.java | 185 ++++++++++++++++++ .../co/arago/hiro/client/rest/AppAPI.java | 16 +- .../co/arago/hiro/client/rest/AuthAPI.java | 6 +- .../client/rest/AuthenticatedAPIHandler.java | 12 +- .../co/arago/hiro/client/rest/GraphAPI.java | 6 +- .../client/websocket/ActionWebSocket.java | 6 +- .../AuthenticatedWebSocketHandler.java | 10 +- .../hiro/client/websocket/EventWebSocket.java | 8 +- .../client/connection/GenericAPITest.java | 4 +- .../token/InvalidCredentialsAPITest.java | 6 +- 13 files changed, 228 insertions(+), 116 deletions(-) create mode 100644 src/main/java/co/arago/hiro/client/connection/token/TokenAPIHandler.java diff --git a/README.md b/README.md index 9d69e88..1514f0a 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ class Example { ## TokenApiHandler Authorization against the HIRO Graph is done via tokens. These tokens are handled by classes of -type `AbstractTokenAPIHandler` in this library. Each of the Hiro-Client-Objects (`GraphAPI`, `AuthAPI`, etc.) need to +type `TokenAPIHandler` in this library. Each of the Hiro-Client-Objects (`GraphAPI`, `AuthAPI`, etc.) need to have some kind of TokenApiHandler in their builder for construction. This library supplies the following TokenApiHandlers: diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java index 79ff8f1..d318c4d 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java @@ -407,8 +407,8 @@ public T setHttpClientAutoClose(boolean httpClientAutoClose) { public static final String version; static { - version = DefaultHttpClientHandler.class.getPackage().getImplementationVersion(); - String t = DefaultHttpClientHandler.class.getPackage().getImplementationTitle(); + version = AbstractAPIHandler.class.getPackage().getImplementationVersion(); + String t = AbstractAPIHandler.class.getPackage().getImplementationTitle(); title = (t != null ? t : "hiro-client-java"); } diff --git a/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java index 9399f84..d6a1691 100644 --- a/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/token/AbstractTokenAPIHandler.java @@ -1,18 +1,13 @@ package co.arago.hiro.client.connection.token; import co.arago.hiro.client.connection.AbstractVersionAPIHandler; -import co.arago.hiro.client.exceptions.AuthenticationTokenException; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.token.DecodedToken; import co.arago.hiro.client.util.httpclient.HttpHeaderMap; -import co.arago.util.json.JsonUtil; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Base64; -public abstract class AbstractTokenAPIHandler extends AbstractVersionAPIHandler implements AutoCloseable { +public abstract class AbstractTokenAPIHandler extends AbstractVersionAPIHandler implements TokenAPIHandler { // ############################################################################################### // ## Conf and Builder ## @@ -47,16 +42,6 @@ public void addToHeaders(HttpHeaderMap headers) { headers.set("User-Agent", userAgent); } - /** - * Return the current token. - * - * @return The current token. - * @throws InterruptedException When call gets interrupted. - * @throws IOException When call has IO errors. - * @throws HiroException On Hiro protocol / handling errors. - */ - public abstract String getToken() throws IOException, InterruptedException, HiroException; - /** * Decode the payload part of the internal token. * @@ -65,69 +50,11 @@ public void addToHeaders(HttpHeaderMap headers) { * @throws IOException When call has IO errors. * @throws HiroException On Hiro protocol / handling errors. */ + @Override public DecodedToken decodeToken() throws HiroException, IOException, InterruptedException { - return decodeToken(getToken()); - } - - /** - * Decode the payload part of any token. - * - * @param token The token to decode. - * @return Decoded token as {@link DecodedToken}. - * @throws IOException When call has IO errors. - * @throws HiroException On Hiro protocol / handling errors. - */ - public static DecodedToken decodeToken(String token) throws HiroException, IOException { - String[] data = token.split("\\."); - - if (data.length == 1) - throw new AuthenticationTokenException("Token is missing base64 encoded data.", 500, token); - - String json = new String(Base64.getUrlDecoder().decode(data[1]), StandardCharsets.UTF_8); - - return JsonUtil.DEFAULT.toObject(json, DecodedToken.class); + return TokenAPIHandler.decodeToken(getToken()); } - /** - * Refresh an invalid token. - * - * @throws InterruptedException When call gets interrupted. - * @throws IOException When call has IO errors. - * @throws HiroException On Hiro protocol / handling errors. - */ - public abstract void refreshToken() throws IOException, InterruptedException, HiroException; - - /** - * Revoke a token - * - * @throws InterruptedException When call gets interrupted. - * @throws IOException When call has IO errors. - * @throws HiroException On Hiro protocol / handling errors. - */ - public abstract void revokeToken() throws IOException, InterruptedException, HiroException; - - /** - * Check for existence of a token in the TokenAPIHandler. - * - * @return true if a token has been set or retrieved, false if the token is empty. - */ - public abstract boolean hasToken(); - - /** - * Check for existence of a refresh token in the TokenAPIHandler. - * - * @return true if a refresh token retrieved, false if no such token exists or these tokens are not applicable for - * this TokenAPIHandler. - */ - public abstract boolean hasRefreshToken(); - - /** - * Calculate the Instant after which the token should be refreshed. - * - * @return The Instant after which the token shall be refreshed. null if token cannot be refreshed. - */ - public abstract Instant expiryInstant(); - /** * Close the underlying httpClientHandler. */ diff --git a/src/main/java/co/arago/hiro/client/connection/token/TokenAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/token/TokenAPIHandler.java new file mode 100644 index 0000000..db00bf4 --- /dev/null +++ b/src/main/java/co/arago/hiro/client/connection/token/TokenAPIHandler.java @@ -0,0 +1,185 @@ +package co.arago.hiro.client.connection.token; + +import co.arago.hiro.client.connection.AbstractAPIHandler; +import co.arago.hiro.client.exceptions.AuthenticationTokenException; +import co.arago.hiro.client.exceptions.HiroException; +import co.arago.hiro.client.exceptions.HiroHttpException; +import co.arago.hiro.client.exceptions.TokenUnauthorizedException; +import co.arago.hiro.client.model.VersionResponse; +import co.arago.hiro.client.model.token.DecodedToken; +import co.arago.hiro.client.util.httpclient.HttpHeaderMap; +import co.arago.util.json.JsonUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Base64; + +public interface TokenAPIHandler extends AutoCloseable { + /** + * Return a copy of the configuration. + * + * @return A copy of the configuration. + * @implNote Please take note, that the included httpClientHandler of this class will be + * added to the returned {@link AbstractAPIHandler.Conf} and therefore will be shared among all + * APIHandlers that use this configuration. + * @see co.arago.hiro.client.rest.AuthenticatedAPIHandler + */ + AbstractAPIHandler.Conf getConf(); + + /** + * Override this to add authentication tokens. TokenHandlers do not have tokens, so this only returns default + * headers. + * + * @param headers Map of headers with initial values. + */ + void addToHeaders(HttpHeaderMap headers); + + /** + * Return the current token. + * + * @return The current token. + * @throws InterruptedException When call gets interrupted. + * @throws IOException When call has IO errors. + * @throws HiroException On Hiro protocol / handling errors. + */ + String getToken() throws IOException, InterruptedException, HiroException; + + /** + * Decode the payload part of the internal token. + * + * @return Decoded token as {@link DecodedToken}. + * @throws InterruptedException When call gets interrupted. + * @throws IOException When call has IO errors. + * @throws HiroException On Hiro protocol / handling errors. + */ + DecodedToken decodeToken() throws HiroException, IOException, InterruptedException; + + /** + * Refresh an invalid token. + * + * @throws InterruptedException When call gets interrupted. + * @throws IOException When call has IO errors. + * @throws HiroException On Hiro protocol / handling errors. + */ + void refreshToken() throws IOException, InterruptedException, HiroException; + + /** + * Revoke a token + * + * @throws InterruptedException When call gets interrupted. + * @throws IOException When call has IO errors. + * @throws HiroException On Hiro protocol / handling errors. + */ + void revokeToken() throws IOException, InterruptedException, HiroException; + + /** + * Check for existence of a token in the TokenAPIHandler. + * + * @return true if a token has been set or retrieved, false if the token is empty. + */ + boolean hasToken(); + + /** + * Check for existence of a refresh token in the TokenAPIHandler. + * + * @return true if a refresh token retrieved, false if no such token exists or these tokens are not applicable for + * this TokenAPIHandler. + */ + boolean hasRefreshToken(); + + /** + * Calculate the Instant after which the token should be refreshed. + * + * @return The Instant after which the token shall be refreshed. null if token cannot be refreshed. + */ + Instant expiryInstant(); + + /** + * The default way to check responses for errors and extract error messages. + * + * @param httpResponse The httpResponse from the HttpRequest + * @param retryCount current counter for retries + * @return true for a retry, false otherwise. + * @throws TokenUnauthorizedException When the statusCode is 401. + * @throws HiroHttpException When the statusCode is < 200 or > 399. + * @throws HiroException On internal errors regarding hiro data processing. + * @throws IOException On IO errors. + * @throws InterruptedException When a call (possibly of an overwritten method) gets interrupted. + */ + boolean checkResponse(HttpResponse httpResponse, int retryCount) + throws HiroException, IOException, InterruptedException; + + /** + * Determine the API URI path for a named API. + * + * @param apiName Name of the API + * @return The URI for that API + * @throws HiroException When the request fails. + * @throws IOException When the connection fails. + * @throws InterruptedException When interrupted. + */ + + URI getApiURIOf(String apiName) throws IOException, InterruptedException, HiroException; + + /** + * Build a complete uri from the webSocketApi and path. + * + * @param path The path to append to webSocketURI. + * @return The constructed URI + */ + URI buildWebSocketURI(String path); + + /** + * @return The HttpClient to use with this class. + */ + HttpClient getOrBuildClient(); + + /** + * Returns the current versionMap. If no versionMap is available, it will be requested, cached + * and then returned. if a sharedConnectionHandler is set, the versionMap will be obtained from there. + * + * @return The (cached) versionMap. + * @throws HiroException When the request fails. + * @throws IOException When the connection fails. + * @throws InterruptedException When interrupted. + */ + VersionResponse getVersionMap() throws HiroException, IOException, InterruptedException; + + /** + * @return Root URI of the API. + */ + URI getRootApiURI(); + + String getUserAgent(); + + /** + * Close the underlying httpClientHandler. + */ + @Override + void close(); + + /** + * Decode the payload part of any token. + * + * @param token The token to decode. + * @return Decoded token as {@link DecodedToken}. + * @throws IOException When call has IO errors. + * @throws HiroException On Hiro protocol / handling errors. + */ + static DecodedToken decodeToken(String token) throws HiroException, IOException { + String[] data = token.split("\\."); + + if (data.length == 1) + throw new AuthenticationTokenException("Token is missing base64 encoded data.", 500, token); + + String json = new String(Base64.getUrlDecoder().decode(data[1]), StandardCharsets.UTF_8); + + return JsonUtil.DEFAULT.toObject(json, DecodedToken.class); + } + +} diff --git a/src/main/java/co/arago/hiro/client/rest/AppAPI.java b/src/main/java/co/arago/hiro/client/rest/AppAPI.java index 5c04ec2..7f3c73f 100644 --- a/src/main/java/co/arago/hiro/client/rest/AppAPI.java +++ b/src/main/java/co/arago/hiro/client/rest/AppAPI.java @@ -1,6 +1,6 @@ package co.arago.hiro.client.rest; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.HiroMessage; import co.arago.hiro.client.model.vertex.HiroVertexListMessage; @@ -25,7 +25,7 @@ public static abstract class Conf> extends AuthenticatedAPIHan public static final class Builder extends Conf { - private Builder(String apiName, AbstractTokenAPIHandler tokenAPIHandler) { + private Builder(String apiName, TokenAPIHandler tokenAPIHandler) { setApiName(apiName); setTokenApiHandler(tokenAPIHandler); } @@ -59,7 +59,7 @@ protected AppAPI(Conf builder) { * @param tokenAPIHandler The API handler for this websocket. * @return The {@link Builder} for {@link AppAPI}. */ - public static Conf newBuilder(AbstractTokenAPIHandler tokenAPIHandler) { + public static Conf newBuilder(TokenAPIHandler tokenAPIHandler) { return new Builder("app", tokenAPIHandler); } @@ -92,7 +92,7 @@ protected GetApplicationCommand self() { /** * @return A {@link HiroVertexMessage} with the result data. - * @throws HiroException When the call returns a http status error. + * @throws HiroException TokenAPIHandler. * @throws IOException When the call got an IO error. * @throws InterruptedException When the call gets interrupted. */ @@ -142,7 +142,7 @@ protected GetConfigCommand self() { /** * @return A {@link HiroMessage} with the result data. - * @throws HiroException When the call returns a http status error. + * @throws HiroException TokenAPIHandler. * @throws IOException When the call got an IO error. * @throws InterruptedException When the call gets interrupted. */ @@ -196,7 +196,7 @@ protected GetContentCommand self() { /** * @return A {@link HttpResponseParser} with the result data. - * @throws HiroException When the call returns a http status error. + * @throws HiroException TokenAPIHandler. * @throws IOException When the call got an IO error. * @throws InterruptedException When the call gets interrupted. */ @@ -249,7 +249,7 @@ protected GetManifestCommand self() { /** * @return A {@link HiroMessage} with the result data. - * @throws HiroException When the call returns a http status error. + * @throws HiroException TokenAPIHandler. * @throws IOException When the call got an IO error. * @throws InterruptedException When the call gets interrupted. */ @@ -299,7 +299,7 @@ protected GetDesktopApplicationsCommand self() { /** * @return A {@link HiroVertexListMessage} with the result data. - * @throws HiroException When the call returns a http status error. + * @throws HiroException TokenAPIHandler. * @throws IOException When the call got an IO error. * @throws InterruptedException When the call gets interrupted. */ diff --git a/src/main/java/co/arago/hiro/client/rest/AuthAPI.java b/src/main/java/co/arago/hiro/client/rest/AuthAPI.java index 68a71b0..acae6d2 100644 --- a/src/main/java/co/arago/hiro/client/rest/AuthAPI.java +++ b/src/main/java/co/arago/hiro/client/rest/AuthAPI.java @@ -1,6 +1,6 @@ package co.arago.hiro.client.rest; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.HiroMessage; import co.arago.hiro.client.model.vertex.HiroVertexListMessage; @@ -29,7 +29,7 @@ public static abstract class Conf> extends AuthenticatedAPIHan public static final class Builder extends Conf { - private Builder(String apiName, AbstractTokenAPIHandler tokenAPIHandler) { + private Builder(String apiName, TokenAPIHandler tokenAPIHandler) { setApiName(apiName); setTokenApiHandler(tokenAPIHandler); } @@ -63,7 +63,7 @@ protected AuthAPI(Conf builder) { * @param tokenAPIHandler The API handler for this websocket. * @return The {@link Builder} for {@link AuthAPI}. */ - public static Conf newBuilder(AbstractTokenAPIHandler tokenAPIHandler) { + public static Conf newBuilder(TokenAPIHandler tokenAPIHandler) { return new Builder("auth", tokenAPIHandler); } diff --git a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java index f3ac6b1..ecb589d 100644 --- a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java @@ -1,7 +1,7 @@ package co.arago.hiro.client.rest; import co.arago.hiro.client.connection.AbstractAPIHandler; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.FixedTokenException; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.HiroHttpException; @@ -29,7 +29,7 @@ /** * This class is the basis of all authenticated API handlers that make use of the different sections of the HIRO API. - * It copies its configuration from the supplied {@link AbstractTokenAPIHandler} and overrides + * It copies its configuration from the supplied {@link TokenAPIHandler} and overrides * {@link AbstractAPIHandler#addToHeaders(HttpHeaderMap)} and {@link AbstractAPIHandler#checkResponse(HttpResponse, int)} * to handle tokens. */ @@ -49,7 +49,7 @@ public static abstract class Conf> { private String apiName; private String apiPath; private Long httpRequestTimeout; - private AbstractTokenAPIHandler tokenAPIHandler; + private TokenAPIHandler tokenAPIHandler; private int maxRetries; public String getApiName() { @@ -105,7 +105,7 @@ public T setMaxRetries(int maxRetries) { return self(); } - public AbstractTokenAPIHandler getTokenApiHandler() { + public TokenAPIHandler getTokenApiHandler() { return this.tokenAPIHandler; } @@ -113,7 +113,7 @@ public AbstractTokenAPIHandler getTokenApiHandler() { * @param tokenAPIHandler The tokenAPIHandler for this API. * @return {@link #self()} */ - public T setTokenApiHandler(AbstractTokenAPIHandler tokenAPIHandler) { + public T setTokenApiHandler(TokenAPIHandler tokenAPIHandler) { this.tokenAPIHandler = tokenAPIHandler; return self(); } @@ -337,7 +337,7 @@ public T setHttpHeaders(HttpHeaderMap headers) { protected final String apiName; protected final String apiPath; - protected final AbstractTokenAPIHandler tokenAPIHandler; + protected final TokenAPIHandler tokenAPIHandler; protected URI apiURI; /** diff --git a/src/main/java/co/arago/hiro/client/rest/GraphAPI.java b/src/main/java/co/arago/hiro/client/rest/GraphAPI.java index 5fe3628..8644be0 100644 --- a/src/main/java/co/arago/hiro/client/rest/GraphAPI.java +++ b/src/main/java/co/arago/hiro/client/rest/GraphAPI.java @@ -1,6 +1,6 @@ package co.arago.hiro.client.rest; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.DefaultHiroItemListMessage; import co.arago.hiro.client.model.HiroMessage; @@ -30,7 +30,7 @@ public static abstract class Conf> extends AuthenticatedAPIHan public static final class Builder extends Conf { - private Builder(String apiName, AbstractTokenAPIHandler tokenAPIHandler) { + private Builder(String apiName, TokenAPIHandler tokenAPIHandler) { setApiName(apiName); setTokenApiHandler(tokenAPIHandler); } @@ -58,7 +58,7 @@ protected GraphAPI(Conf builder) { super(builder); } - public static Conf newBuilder(AbstractTokenAPIHandler tokenAPIHandler) { + public static Conf newBuilder(TokenAPIHandler tokenAPIHandler) { return new Builder("graph", tokenAPIHandler); } diff --git a/src/main/java/co/arago/hiro/client/websocket/ActionWebSocket.java b/src/main/java/co/arago/hiro/client/websocket/ActionWebSocket.java index e2ee8b8..479b622 100644 --- a/src/main/java/co/arago/hiro/client/websocket/ActionWebSocket.java +++ b/src/main/java/co/arago/hiro/client/websocket/ActionWebSocket.java @@ -1,6 +1,6 @@ package co.arago.hiro.client.websocket; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.WebSocketException; import co.arago.hiro.client.model.HiroMessage; @@ -78,7 +78,7 @@ public T setActionWebSocketListener(ActionWebSocketListener actionWebSocketListe public static final class Builder extends Conf { - private Builder(AbstractTokenAPIHandler tokenAPIHandler, ActionWebSocketListener webSocketListener) { + private Builder(TokenAPIHandler tokenAPIHandler, ActionWebSocketListener webSocketListener) { setTokenApiHandler(tokenAPIHandler); setActionWebSocketListener(webSocketListener); } @@ -410,7 +410,7 @@ protected ActionWebSocket(Conf builder) { * @return The {@link ActionWebSocket.Builder} for {@link ActionWebSocket}. */ public static Conf newBuilder( - AbstractTokenAPIHandler tokenAPIHandler, + TokenAPIHandler tokenAPIHandler, ActionWebSocketListener actionWebSocketListener) { return new ActionWebSocket.Builder(tokenAPIHandler, actionWebSocketListener); } diff --git a/src/main/java/co/arago/hiro/client/websocket/AuthenticatedWebSocketHandler.java b/src/main/java/co/arago/hiro/client/websocket/AuthenticatedWebSocketHandler.java index d0b87a4..26ee21d 100644 --- a/src/main/java/co/arago/hiro/client/websocket/AuthenticatedWebSocketHandler.java +++ b/src/main/java/co/arago/hiro/client/websocket/AuthenticatedWebSocketHandler.java @@ -1,7 +1,7 @@ package co.arago.hiro.client.websocket; import co.arago.hiro.client.connection.AbstractAPIHandler; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.RefreshTokenWebSocketException; import co.arago.hiro.client.exceptions.UnauthorizedWebSocketException; @@ -60,7 +60,7 @@ public static abstract class Conf> { private final HttpHeaderMap headers = new HttpHeaderMap(); private String fragment; private long webSocketMessageTimeout = 60000L; - private AbstractTokenAPIHandler tokenAPIHandler; + private TokenAPIHandler tokenAPIHandler; private int maxRetries = 2; private boolean reconnectOnFailedSend = false; @@ -218,7 +218,7 @@ public T setMaxRetries(int maxRetries) { return self(); } - public AbstractTokenAPIHandler getTokenApiHandler() { + public TokenAPIHandler getTokenApiHandler() { return this.tokenAPIHandler; } @@ -226,7 +226,7 @@ public AbstractTokenAPIHandler getTokenApiHandler() { * @param tokenAPIHandler The tokenAPIHandler for this API. * @return {@link #self()} */ - public T setTokenApiHandler(AbstractTokenAPIHandler tokenAPIHandler) { + public T setTokenApiHandler(TokenAPIHandler tokenAPIHandler) { this.tokenAPIHandler = tokenAPIHandler; return self(); } @@ -492,7 +492,7 @@ public enum Status { protected final String endpoint; protected final String protocol; protected final int maxRetries; - protected final AbstractTokenAPIHandler tokenAPIHandler; + protected final TokenAPIHandler tokenAPIHandler; protected final URIEncodedData query = new URIEncodedData(); protected final HttpHeaderMap headers = new HttpHeaderMap(); protected final String fragment; diff --git a/src/main/java/co/arago/hiro/client/websocket/EventWebSocket.java b/src/main/java/co/arago/hiro/client/websocket/EventWebSocket.java index ef22c78..c883321 100644 --- a/src/main/java/co/arago/hiro/client/websocket/EventWebSocket.java +++ b/src/main/java/co/arago/hiro/client/websocket/EventWebSocket.java @@ -1,6 +1,6 @@ package co.arago.hiro.client.websocket; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.model.HiroMessage; import co.arago.hiro.client.model.websocket.events.EventsFilter; @@ -223,7 +223,7 @@ public T setEventWebSocketListener(EventWebSocketListener eventWebSocketListener public static final class Builder extends Conf { - private Builder(AbstractTokenAPIHandler tokenAPIHandler, EventWebSocketListener webSocketListener) { + private Builder(TokenAPIHandler tokenAPIHandler, EventWebSocketListener webSocketListener) { setTokenApiHandler(tokenAPIHandler); setEventWebSocketListener(webSocketListener); } @@ -387,7 +387,7 @@ private Long msTillNextStart() { private final Map eventsFilterMap; /** - * Protected Constructor. Use {@link #newBuilder(AbstractTokenAPIHandler, EventWebSocketListener)}. + * Protected Constructor. Use {@link #newBuilder(TokenAPIHandler, EventWebSocketListener)}. * * @param builder The {@link Builder} to use. */ @@ -410,7 +410,7 @@ protected EventWebSocket(Conf builder) { * @return The {@link Builder} for {@link EventWebSocket}. */ public static Conf newBuilder( - AbstractTokenAPIHandler tokenAPIHandler, + TokenAPIHandler tokenAPIHandler, EventWebSocketListener eventWebSocketListener) { return new EventWebSocket.Builder(tokenAPIHandler, eventWebSocketListener); } diff --git a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java index 9772322..e847307 100644 --- a/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/GenericAPITest.java @@ -1,8 +1,8 @@ package co.arago.hiro.client.connection; import co.arago.hiro.client.ConfigModel; -import co.arago.hiro.client.connection.token.AbstractTokenAPIHandler; import co.arago.hiro.client.connection.token.PasswordAuthTokenAPIHandler; +import co.arago.hiro.client.connection.token.TokenAPIHandler; import co.arago.hiro.client.exceptions.HiroException; import co.arago.hiro.client.exceptions.HiroHttpException; import co.arago.hiro.client.mock.MockGraphitServerExtension; @@ -29,7 +29,7 @@ @ExtendWith(MockGraphitServerExtension.class) public class GenericAPITest { - public static AbstractTokenAPIHandler handler; + public static TokenAPIHandler handler; public static AuthenticatedAPIHandler apiHandler; final static Logger log = LoggerFactory.getLogger(GenericAPITest.class); diff --git a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java index caa1d52..8ae7d10 100644 --- a/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java +++ b/src/test/java/co/arago/hiro/client/connection/token/InvalidCredentialsAPITest.java @@ -36,7 +36,7 @@ void wrongCredentials() { if (config == null) return; - try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + try (TokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() .setAcceptAllCerts(config.accept_all_certs) .setShutdownTimeout(0) .setRootApiURI(config.api_url) @@ -64,7 +64,7 @@ void wrongClient() { if (config == null) return; - try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + try (TokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() .setAcceptAllCerts(config.accept_all_certs) .setShutdownTimeout(0) .setRootApiURI(config.api_url) @@ -92,7 +92,7 @@ void wrongUrl() { if (config == null) return; - try (AbstractTokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() + try (TokenAPIHandler handler = PasswordAuthTokenAPIHandler.newBuilder() .setAcceptAllCerts(config.accept_all_certs) .setShutdownTimeout(0) .setCredentials(config.username, config.password, config.client_id, config.client_secret) From 40a7aa44de7a42815d9292e591e2d13f95ad4960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 14:20:29 +0200 Subject: [PATCH 4/8] Adding final --- .../co/arago/hiro/client/rest/AuthenticatedAPIHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java index ecb589d..6a1e6f1 100644 --- a/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/rest/AuthenticatedAPIHandler.java @@ -136,8 +136,8 @@ public T setTokenApiHandler(TokenAPIHandler tokenAPIHandler) { public static abstract class APIRequestConf, R> { protected final URIPath path; - protected URIEncodedData query = new URIEncodedData(); - protected HttpHeaderMap headers = new HttpHeaderMap(); + protected final URIEncodedData query = new URIEncodedData(); + protected final HttpHeaderMap headers = new HttpHeaderMap(); protected String fragment; protected Long httpRequestTimeout; From 91a7e4000e60da79fe9f5d52058c222f2af1e1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 16:18:03 +0200 Subject: [PATCH 5/8] Documentation / Fixed string httpclient -> http --- .../client/connection/AbstractAPIHandler.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java index d318c4d..7d74515 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java @@ -182,7 +182,8 @@ public HttpClientHandler.ProxySpec getProxy() { /** * @param proxy Simple proxy with one address and port * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setProxy(HttpClientHandler.ProxySpec proxy) { @@ -198,7 +199,8 @@ public boolean isFollowRedirects() { /** * @param followRedirects Enable Redirect.NORMAL. Default is true. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setFollowRedirects(boolean followRedirects) { @@ -214,7 +216,8 @@ public Long getConnectTimeout() { /** * @param connectTimeout Connect timeout in milliseconds. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setConnectTimeout(Long connectTimeout) { @@ -232,7 +235,8 @@ public long getShutdownTimeout() { * If this is set to a value too low, you might need to wait elsewhere for the HttpClient * to shut down properly. Default is 3000ms. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setShutdownTimeout(long shutdownTimeout) { @@ -251,7 +255,8 @@ public Boolean getAcceptAllCerts() { * * @param acceptAllCerts the toggle * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setAcceptAllCerts(Boolean acceptAllCerts) { @@ -267,8 +272,9 @@ public SSLContext getSslContext() { /** * @param sslContext The specific SSLContext to use. * @return {@link #self()} + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. * @see #setAcceptAllCerts(Boolean) - * @implNote Will be ignored if {@link #httpClientHandler} is set. */ @Override public T setSslContext(SSLContext sslContext) { @@ -284,7 +290,8 @@ public SSLParameters getSslParameters() { /** * @param sslParameters The specific SSLParameters to use. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setSslParameters(SSLParameters sslParameters) { @@ -303,10 +310,12 @@ public HttpClient getHttpClient() { * * @param httpClient Instance of an HttpClient. * @return {@link #self()} - * @implNote Be aware, that any httpClient given via this method will be marked as external and has to be - * closed externally as well. A call to {@link DefaultHttpClientHandler#close()} with an external httpclient - * will have no effect. - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + *
+ * Be aware, that any httpClient given via this method will set AutoClose to false and has to be + * closed externally, unless {@link #setHttpClientAutoClose(boolean)} is used. A call to + * {@link HttpClientHandler#close()} with AutoClose set to false will have no effect. */ @Override public T setHttpClient(HttpClient httpClient) { @@ -325,7 +334,8 @@ public CookieManager getCookieManager() { * * @param cookieManager Instance of a CookieManager. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setCookieManager(CookieManager cookieManager) { @@ -344,7 +354,8 @@ public int getMaxConnectionPool() { * * @param maxConnectionPool Maximum size of the pool. Default is 8. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setMaxConnectionPool(int maxConnectionPool) { @@ -362,7 +373,8 @@ public int getMaxBinaryLogLength() { * * @param maxBinaryLogLength Size in bytes * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setMaxBinaryLogLength(int maxBinaryLogLength) { @@ -386,7 +398,8 @@ public Boolean getHttpClientAutoClose() { * * @param httpClientAutoClose true: enable, false: disable. * @return {@link #self()} - * @implNote Will be ignored if {@link #httpClientHandler} is set. + * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setHttpClientAutoClose(boolean httpClientAutoClose) { @@ -475,7 +488,7 @@ public URI getRootApiURI() { public URI getWebSocketURI() { try { return (webSocketURI != null ? webSocketURI - : new URI(RegExUtils.replaceFirst(getRootApiURI().toString(), "^httpclient", "ws"))); + : new URI(RegExUtils.replaceFirst(getRootApiURI().toString(), "^http", "ws"))); } catch (URISyntaxException e) { throw new IllegalArgumentException("Cannot create webSocketURI from rootApiURI.", e); } From b284c616fbdd9178695cc720bbc3f4cce0f9fadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 16:45:32 +0200 Subject: [PATCH 6/8] Small optimizations --- .../client/util/httpclient/MultiValueMap.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/co/arago/hiro/client/util/httpclient/MultiValueMap.java b/src/main/java/co/arago/hiro/client/util/httpclient/MultiValueMap.java index 121a3a0..2c43356 100644 --- a/src/main/java/co/arago/hiro/client/util/httpclient/MultiValueMap.java +++ b/src/main/java/co/arago/hiro/client/util/httpclient/MultiValueMap.java @@ -34,7 +34,7 @@ public MultiValueMap(Map initialValues) { } public MultiValueMap(MultiValueMap other) { - map.putAll(other.map); + appendAll(other); } /** @@ -62,11 +62,7 @@ public void setAll(Map initialValues) { */ public void setAll(MultiValueMap other) { map.clear(); - - if (other == null) - return; - - map.putAll(other.map); + appendAll(other); } /** @@ -100,7 +96,7 @@ public void appendAll(MultiValueMap other) { if (other == null) return; - other.map.forEach(this::appendAtKey); + other.map.forEach((key, values) -> map.put(key, new ArrayList<>(values))); } /** @@ -228,9 +224,9 @@ public int hashCode() { /** * @return A deep copy of the internal map. */ - public Map> getCopy() { + public Map> getMapCopy() { Map> clone = new LinkedHashMap<>(); - map.forEach((key, value) -> clone.put(key, new ArrayList<>(value))); + map.forEach((key, values) -> clone.put(key, new ArrayList<>(values))); return clone; } @@ -239,7 +235,7 @@ public Map> getCopy() { */ public Map toSingleValueMap() { Map singleValueMap = new HashMap<>(); - map.forEach((key, value) -> singleValueMap.put(key, value.iterator().next())); + map.forEach((key, values) -> singleValueMap.put(key, values.iterator().next())); return singleValueMap; } From f2780e3f585a94bbe52faac2c22f6da5e5efdfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 16:46:01 +0200 Subject: [PATCH 7/8] Documentation and no toASCIIString --- .../client/connection/AbstractAPIHandler.java | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java index 7d74515..06c61e8 100644 --- a/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java +++ b/src/main/java/co/arago/hiro/client/connection/AbstractAPIHandler.java @@ -183,7 +183,7 @@ public HttpClientHandler.ProxySpec getProxy() { * @param proxy Simple proxy with one address and port * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setProxy(HttpClientHandler.ProxySpec proxy) { @@ -200,7 +200,7 @@ public boolean isFollowRedirects() { * @param followRedirects Enable Redirect.NORMAL. Default is true. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setFollowRedirects(boolean followRedirects) { @@ -217,7 +217,7 @@ public Long getConnectTimeout() { * @param connectTimeout Connect timeout in milliseconds. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setConnectTimeout(Long connectTimeout) { @@ -236,7 +236,7 @@ public long getShutdownTimeout() { * to shut down properly. Default is 3000ms. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setShutdownTimeout(long shutdownTimeout) { @@ -256,7 +256,7 @@ public Boolean getAcceptAllCerts() { * @param acceptAllCerts the toggle * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setAcceptAllCerts(Boolean acceptAllCerts) { @@ -273,7 +273,7 @@ public SSLContext getSslContext() { * @param sslContext The specific SSLContext to use. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. * @see #setAcceptAllCerts(Boolean) */ @Override @@ -291,7 +291,7 @@ public SSLParameters getSslParameters() { * @param sslParameters The specific SSLParameters to use. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setSslParameters(SSLParameters sslParameters) { @@ -311,11 +311,11 @@ public HttpClient getHttpClient() { * @param httpClient Instance of an HttpClient. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. - *
- * Be aware, that any httpClient given via this method will set AutoClose to false and has to be - * closed externally, unless {@link #setHttpClientAutoClose(boolean)} is used. A call to - * {@link HttpClientHandler#close()} with AutoClose set to false will have no effect. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + *
+ * Be aware, that any httpClient given via this method will set AutoClose to false and has to be + * closed externally, unless {@link #setHttpClientAutoClose(boolean)} is used. A call to + * {@link HttpClientHandler#close()} with AutoClose set to false will have no effect. */ @Override public T setHttpClient(HttpClient httpClient) { @@ -335,7 +335,7 @@ public CookieManager getCookieManager() { * @param cookieManager Instance of a CookieManager. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setCookieManager(CookieManager cookieManager) { @@ -355,7 +355,7 @@ public int getMaxConnectionPool() { * @param maxConnectionPool Maximum size of the pool. Default is 8. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setMaxConnectionPool(int maxConnectionPool) { @@ -374,7 +374,7 @@ public int getMaxBinaryLogLength() { * @param maxBinaryLogLength Size in bytes * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setMaxBinaryLogLength(int maxBinaryLogLength) { @@ -399,7 +399,7 @@ public Boolean getHttpClientAutoClose() { * @param httpClientAutoClose true: enable, false: disable. * @return {@link #self()} * @implNote Configuration option for an internal DefaultHttpClientHandler. Will be ignored if the - * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. + * {@link #httpClientHandler} is set directly via {@link #setHttpClientHandler(HttpClientHandler)}. */ @Override public T setHttpClientAutoClose(boolean httpClientAutoClose) { @@ -467,8 +467,8 @@ protected AbstractAPIHandler(Conf builder) { * * @return A copy of the configuration. * @implNote Please take note, that the included httpClientHandler of this class will be - * added to the returned {@link Conf} and therefore will be shared among all APIHandlers that use this - * configuration. + * added to the returned {@link Conf} and therefore will be shared among all APIHandlers that use this + * configuration. * @see co.arago.hiro.client.rest.AuthenticatedAPIHandler */ public Conf getConf() { @@ -556,17 +556,19 @@ public static URI buildURI(URI uri, String path, boolean finalSlash) { * a query already. * @param fragment URI Fragment. Can be null for no fragment, otherwise uri must not have a fragment already. * @return The constructed URI + * @throws IllegalArgumentException When the given URI already has a query and the given query is not blank or + * the URI has a fragment and the given fragment is not blank. */ public static URI addQueryFragmentAndNormalize(URI uri, URIEncodedData query, String fragment) { - String sourceURI = uri.toASCIIString(); + String sourceURI = uri.toString(); if (query != null) { String encodedQueryString = query.toString(); if (StringUtils.isNotBlank(encodedQueryString)) { if (sourceURI.contains("?")) { - throw new IllegalArgumentException("Given uri must not have a query part already."); + throw new IllegalArgumentException("Given URI must not have a query part already."); } sourceURI += "?" + encodedQueryString; } @@ -574,7 +576,7 @@ public static URI addQueryFragmentAndNormalize(URI uri, URIEncodedData query, St if (StringUtils.isNotBlank(fragment)) { if (sourceURI.contains("#")) { - throw new IllegalArgumentException("Given uri must not have a fragment part already."); + throw new IllegalArgumentException("Given URI must not have a fragment part already."); } sourceURI += "#" + URLPartEncoder.encodeNoPlus(fragment, StandardCharsets.UTF_8); } @@ -586,13 +588,6 @@ public static URI addQueryFragmentAndNormalize(URI uri, URIEncodedData query, St } } - private static void addQueryPart(StringBuilder builder, String key, String value) { - builder - .append(URLPartEncoder.encodeNoPlus(key, StandardCharsets.UTF_8)) - .append("=") - .append(URLPartEncoder.encodeNoPlus(value, StandardCharsets.UTF_8)); - } - /** * Create a HttpRequest.Builder with common options and headers. * @@ -639,10 +634,10 @@ public HttpRequest.Builder getRequestBuilder( * @return The constructed HttpRequest. */ private HttpRequest createStreamRequest(URI uri, - String method, - StreamContainer body, - HttpHeaderMap headers, - Long httpRequestTimeout) throws InterruptedException, IOException, HiroException { + String method, + StreamContainer body, + HttpHeaderMap headers, + Long httpRequestTimeout) throws InterruptedException, IOException, HiroException { if (body != null && body.hasContentType()) { headers.set("Content-Type", body.getContentType()); @@ -671,10 +666,10 @@ private HttpRequest createStreamRequest(URI uri, * @return The constructed HttpRequest. */ private HttpRequest createStringRequest(URI uri, - String method, - String body, - HttpHeaderMap headers, - Long httpRequestTimeout) throws InterruptedException, IOException, HiroException { + String method, + String body, + HttpHeaderMap headers, + Long httpRequestTimeout) throws InterruptedException, IOException, HiroException { HttpRequest httpRequest = getRequestBuilder(uri, headers, httpRequestTimeout) .method(method, @@ -697,7 +692,7 @@ private HttpRequest createStringRequest(URI uri, * @param httpRequest The httpRequest to send * @param maxRetries The amount of retries on errors. When this is null, {@link #maxRetries} will be used. * @return A HttpResponse containing an InputStream of the incoming body part of - * the result. + * the result. * @throws HiroException When status errors occur. * @throws IOException On IO errors with the connection. * @throws InterruptedException When the call gets interrupted. From 8b57f04c8a3589ce6f03eded9084fdb9b510e279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20H=C3=BCbner?= Date: Tue, 26 Jul 2022 16:53:41 +0200 Subject: [PATCH 8/8] Changed CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e554f1c..aa57280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# v0.5.0 + +Refactorings + +* Made `AbstractClientAPIHandler` into `DefaultHttpClientHandler` without references to `AbstractAPIHandler`. +* Created interfaces HttpClientHandler and TokenAPIHandler. +* Got rid of the copy constructors. +* Added and modified documentation. +* Do not ignore `FixedTokenException` when a token is invalid and cannot be changed, but add it to the exception chain. + + # v0.4.0 * Code Auth and Org switch.