Skip to content

Commit 588f4f4

Browse files
authored
feat(java-sdk): allow passing OkHttpClient objects (#195)
relates to STACKITSDK-238
1 parent 33a35e0 commit 588f4f4

File tree

9 files changed

+328
-40
lines changed

9 files changed

+328
-40
lines changed

openapi-generator-config-java.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ files:
1818
custom/serviceApi.mustache:
1919
templateType: API
2020
destinationFilename: ServiceApi.java
21+
custom/serviceApiTest.mustache:
22+
templateType: APITests
23+
destinationFilename: ServiceApiTest.java

scripts/generate-sdk/.openapi-generator-ignore-java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ gradle/**
2626
# ApiException from core library should be used (avoid multiple ApiException imports in case different services are used at the same time)
2727
**/ApiException.java
2828

29-
# Authentication classes are not required because the KeyFlowAuth is attached as interceptor
29+
# Authentication classes are not required because the KeyFlowAuth is attached as authenticator
3030
**/auth/**
3131

3232
# Service Configuration is not required. It only stores a defaultApiClient

scripts/generate-sdk/languages/java.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ generate_java_sdk() {
118118
--git-host "${GIT_HOST}" \
119119
--git-user-id "${GIT_USER_ID}" \
120120
--git-repo-id "${GIT_REPO_ID}" \
121-
--global-property apis,models,modelTests=false,modelDocs=false,apiDocs=false,apiTests=false,supportingFiles \
121+
--global-property apis,models,modelTests=false,modelDocs=false,apiDocs=false,apiTests=true,supportingFiles \
122122
--additional-properties="artifactId=${service},artifactDescription=${SERVICE_DESCRIPTION},invokerPackage=cloud.stackit.sdk.${service},modelPackage=cloud.stackit.sdk.${service}.model,apiPackage=cloud.stackit.sdk.${service}.api,serviceName=${service_pascal_case}" >/dev/null \
123123
--http-user-agent stackit-sdk-java/"${service}" \
124124
--config openapi-generator-config-java.yml
@@ -129,6 +129,10 @@ generate_java_sdk() {
129129
if [ -f "$api_file" ]; then
130130
mv "$api_file" "${SERVICES_FOLDER}/${service}/src/main/java/cloud/stackit/sdk/${service}/api/${service_pascal_case}Api.java"
131131
fi
132+
api_test_file="${SERVICES_FOLDER}/${service}/src/test/java/cloud/stackit/sdk/${service}/api/DefaultApiTestServiceApiTest.java"
133+
if [ -f "$api_test_file" ]; then
134+
mv "$api_test_file" "${SERVICES_FOLDER}/${service}/src/test/java/cloud/stackit/sdk/${service}/api/${service_pascal_case}ApiTest.java"
135+
fi
132136

133137
# Remove unnecessary files
134138
rm "${SERVICES_FOLDER}/${service}/.openapi-generator-ignore"

templates/java/ApiClient.mustache

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {{invokerPackage}}.auth.AWS4Auth;
7878
import cloud.stackit.sdk.core.auth.SetupAuth;
7979
import cloud.stackit.sdk.core.config.CoreConfiguration;
8080
import cloud.stackit.sdk.core.exception.ApiException;
81+
import cloud.stackit.sdk.core.KeyFlowAuthenticator;
8182

8283
/**
8384
* <p>ApiClient class.</p>
@@ -130,50 +131,75 @@ public class ApiClient {
130131
protected JSON json;
131132

132133
protected HttpLoggingInterceptor loggingInterceptor;
133-
protected SetupAuth authenticationInterceptor;
134134

135135
protected CoreConfiguration configuration;
136136

137137
{{#dynamicOperations}}
138138
protected Map<String, ApiOperation> operationLookupMap = new HashMap<>();
139139

140140
{{/dynamicOperations}}
141-
/**
142-
* Basic constructor for ApiClient
141+
{{! BEGIN - Removed ApiClient constructor and replaced it with a custom constructors which create the ApiClient with the CoreConfiguration }}
142+
/**
143+
* Basic constructor for ApiClient.
144+
*
145+
* Not recommended for production use, use the one with the OkHttpClient parameter instead.
146+
*
147+
* @throws IOException thrown when a file can not be found
143148
*/
144149
public ApiClient() throws IOException {
145-
{{! BEGIN - replace basic constructur }}
146-
this(new CoreConfiguration());
147-
{{! END - replace basic constructur }}
150+
this(null, new CoreConfiguration());
148151
}
149152

150-
{{! BEGIN - Removed ApiClient constructor with OkHttpClient as param and replaced it with a custom constructor which creates the ApiClient with the CoreConfiguration }}
151153
/**
152-
* Basic constructor with custom CoreConfiguration
153-
*
154-
* @param config a {@link cloud.stackit.sdk.core.config} object
155-
* @throws IOException thrown when a file can not be found
156-
*/
154+
* Basic constructor for ApiClient
155+
*
156+
* Not recommended for production use, use the one with the OkHttpClient parameter instead.
157+
*
158+
* @param config a {@link cloud.stackit.sdk.core.config.CoreConfiguration} object
159+
* @throws IOException thrown when a file can not be found
160+
*/
157161
public ApiClient(CoreConfiguration config) throws IOException {
162+
this(null, config);
163+
}
164+
165+
/**
166+
* Constructor for ApiClient with OkHttpClient parameter. Recommended for production use.
167+
*
168+
* @param httpClient a OkHttpClient object
169+
* @throws IOException thrown when a file can not be found
170+
*/
171+
public ApiClient(OkHttpClient httpClient) throws IOException {
172+
this(httpClient, new CoreConfiguration());
173+
}
174+
175+
/**
176+
* Constructor for ApiClient with OkHttpClient parameter. Recommended for production use.
177+
*
178+
* @param httpClient a OkHttpClient object
179+
* @param config a {@link cloud.stackit.sdk.core.config.CoreConfiguration} object
180+
* @throws IOException thrown when a file can not be found
181+
*/
182+
public ApiClient(OkHttpClient httpClient, CoreConfiguration config) throws IOException {
158183
init();
159184
160-
if (config.getCustomEndpoint() != null && !config.getCustomEndpoint().trim().isEmpty()) {
161-
basePath = config.getCustomEndpoint();
162-
}
163-
if (config.getDefaultHeader() != null) {
164-
defaultHeaderMap = config.getDefaultHeader();
165-
}
185+
if (config.getCustomEndpoint() != null && !config.getCustomEndpoint().trim().isEmpty()) {
186+
basePath = config.getCustomEndpoint();
187+
}
188+
if (config.getDefaultHeader() != null) {
189+
defaultHeaderMap = config.getDefaultHeader();
190+
}
166191
this.configuration = config;
167192

168-
// Setup AuthHandler
169-
SetupAuth auth;
170-
auth = new SetupAuth(config);
171-
auth.init();
172-
authenticationInterceptor = auth;
173-
174-
initHttpClient();
193+
if (httpClient == null) {
194+
initHttpClient();
195+
KeyFlowAuthenticator authenticator = new KeyFlowAuthenticator(this.httpClient, config);
196+
this.httpClient = this.httpClient.newBuilder().authenticator(authenticator).build();
197+
} else {
198+
// Authorization has to be configured manually in case a custom http client object is passed
199+
this.httpClient = httpClient;
200+
}
175201
}
176-
{{! END - Removed ApiClient constructor with OkHttpClient as param and replaced it with a custom constructor which creates the ApiClient with the CoreConfiguration }}
202+
{{! END - Removed ApiClient constructor and replaced it with a custom constructors which create the ApiClient with the CoreConfiguration }}
177203

178204
{{#hasOAuthMethods}}
179205
{{#oauthMethods}}
@@ -261,8 +287,6 @@ public class ApiClient {
261287
for (Interceptor interceptor: interceptors) {
262288
builder.addInterceptor(interceptor);
263289
}
264-
// Adds the Authorization header to requests
265-
builder.addInterceptor(authenticationInterceptor.getAuthHandler());
266290
{{#useGzipFeature}}
267291
// Enable gzip request compression
268292
builder.addInterceptor(new GzipRequestInterceptor());
@@ -334,7 +358,15 @@ public class ApiClient {
334358
return this;
335359
}
336360

337-
{{! remove getter and setter for httpClient because this would configure a client without the authentication interceptor }}
361+
/**
362+
* Get HTTP client
363+
*
364+
* @return An instance of OkHttpClient
365+
*/
366+
public OkHttpClient getHttpClient() {
367+
return httpClient;
368+
}
369+
{{! remove setter for httpClient because this would configure a client without the authentication interceptor }}
338370
{{! if this will be requested in a feature-request, it should be implemented with the core configuration module like in the Go SDK }}
339371

340372
/**

templates/java/api.mustache

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import java.io.InputStream;
5454
{{/supportStreaming}}
5555
import cloud.stackit.sdk.core.config.CoreConfiguration;
5656
import cloud.stackit.sdk.core.exception.ApiException;
57+
import okhttp3.OkHttpClient;
5758

5859
{{#operations}}
5960
// Package-private access to enforce service-specific API usage (DefaultApi => <ServiceName>Api)
@@ -63,16 +64,52 @@ class {{classname}} {
6364
private String localCustomBaseUrl;
6465
6566
{{! BEGIN - Remove default constructor and replaced with constructor which uses CoreConfiguration }}
66-
public {{classname}}() throws IOException {
67-
this(new CoreConfiguration());
68-
}
69-
70-
public {{classname}}(CoreConfiguration config) throws IOException {
71-
if (config.getCustomEndpoint() != null && !config.getCustomEndpoint().trim().isEmpty()) {
67+
/**
68+
* Basic constructor for DefaultApi
69+
*
70+
* For production use consider using the constructor with the OkHttpClient parameter.
71+
*
72+
* @throws IOException
73+
*/
74+
public DefaultApi() throws IOException {
75+
this(null, new CoreConfiguration());
76+
}
77+
78+
/**
79+
* Basic Constructor for DefaultApi
80+
*
81+
* For production use consider using the constructor with the OkHttpClient parameter.
82+
*
83+
* @param config your STACKIT SDK CoreConfiguration
84+
* @throws IOException
85+
*/
86+
public DefaultApi(CoreConfiguration config) throws IOException {
87+
this(null, config);
88+
}
89+
90+
/**
91+
* Constructor for DefaultApi
92+
*
93+
* @param httpClient OkHttpClient object
94+
* @throws IOException
95+
*/
96+
public DefaultApi(OkHttpClient httpClient) throws IOException {
97+
this(httpClient, new CoreConfiguration());
98+
}
99+
100+
/**
101+
* Constructor for DefaultApi
102+
*
103+
* @param httpClient OkHttpClient object
104+
* @param config your STACKIT SDK CoreConfiguration
105+
* @throws IOException
106+
*/
107+
public DefaultApi(OkHttpClient httpClient, CoreConfiguration config) throws IOException {
108+
if (config.getCustomEndpoint() != null && !config.getCustomEndpoint().trim().isEmpty()) {
72109
localCustomBaseUrl = config.getCustomEndpoint();
73110
}
74-
this.localVarApiClient = new ApiClient(config);
75-
}
111+
this.localVarApiClient = new ApiClient(httpClient, config);
112+
}
76113
{{! END - Remove default constructor and replaced with constructor which uses CoreConfiguration }}
77114

78115
public ApiClient getApiClient() {

templates/java/api_test.mustache

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{{! This template had to be customized because of our changes to the DefaultApi and ApiClient classes }}
2+
{{! Original template: https://github.com/OpenAPITools/openapi-generator/blob/v7.15.0/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/api_test.mustache }}
3+
4+
{{>licenseInfo}}
5+
6+
package {{invokerPackage}};
7+
8+
import cloud.stackit.sdk.core.KeyFlowAuthenticator;
9+
import cloud.stackit.sdk.core.auth.SetupAuth;
10+
import cloud.stackit.sdk.core.config.CoreConfiguration;
11+
import cloud.stackit.sdk.core.utils.TestUtils;
12+
import java.io.IOException;
13+
import okhttp3.Authenticator;
14+
import okhttp3.OkHttpClient;
15+
import org.junit.jupiter.api.Assertions;
16+
import org.junit.jupiter.api.Test;
17+
18+
public class DefaultApiTest {
19+
@Test
20+
public void TestCustomHttpClient() throws IOException {
21+
// before
22+
CoreConfiguration conf =
23+
new CoreConfiguration().serviceAccountKey(TestUtils.MOCK_SERVICE_ACCOUNT_KEY);
24+
25+
// when
26+
OkHttpClient httpClient = new OkHttpClient();
27+
ApiClient apiClient = new ApiClient(httpClient, conf);
28+
29+
// then
30+
Assertions.assertEquals(httpClient, apiClient.getHttpClient());
31+
// make sure the http client object is exactly the same object
32+
Assertions.assertSame(httpClient, apiClient.getHttpClient());
33+
}
34+
35+
@Test
36+
public void TestNoCustomHttpClient() throws IOException {
37+
// before
38+
CoreConfiguration conf =
39+
new CoreConfiguration().serviceAccountKey(TestUtils.MOCK_SERVICE_ACCOUNT_KEY);
40+
41+
// when
42+
ApiClient apiClient = new ApiClient(conf);
43+
44+
// then
45+
/*
46+
* verify a fresh OkHttpClient got created which will have the auth header set
47+
* by the {@link cloud.stackit.sdk.core.KeyFlowAuthenticator}
48+
*/
49+
OkHttpClient httpClient = new OkHttpClient();
50+
Authenticator authenticator =
51+
new KeyFlowAuthenticator(httpClient, conf, SetupAuth.setupKeyFlow(conf));
52+
httpClient = httpClient.newBuilder().authenticator(authenticator).build();
53+
54+
Assertions.assertNotNull(apiClient.getHttpClient());
55+
Assertions.assertEquals(
56+
httpClient.authenticator().getClass(),
57+
apiClient.getHttpClient().authenticator().getClass());
58+
}
59+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{{>licenseInfo}}
2+
3+
package {{invokerPackage}};
4+
5+
import cloud.stackit.sdk.core.KeyFlowAuthenticator;
6+
import cloud.stackit.sdk.core.auth.SetupAuth;
7+
import cloud.stackit.sdk.core.config.CoreConfiguration;
8+
import cloud.stackit.sdk.core.utils.TestUtils;
9+
import java.io.IOException;
10+
import okhttp3.Authenticator;
11+
import okhttp3.OkHttpClient;
12+
import org.junit.jupiter.api.Assertions;
13+
import org.junit.jupiter.api.Test;
14+
15+
/** Tests for ApiClient */
16+
public class ApiClientTest {
17+
@Test
18+
public void TestCustomHttpClient() throws IOException {
19+
// before
20+
CoreConfiguration conf =
21+
new CoreConfiguration().serviceAccountKey(TestUtils.MOCK_SERVICE_ACCOUNT_KEY);
22+
23+
// when
24+
OkHttpClient httpClient = new OkHttpClient();
25+
ApiClient apiClient = new ApiClient(httpClient, conf);
26+
27+
// then
28+
Assertions.assertEquals(httpClient, apiClient.getHttpClient());
29+
// make sure the http client object is exactly the same object
30+
Assertions.assertSame(httpClient, apiClient.getHttpClient());
31+
}
32+
33+
@Test
34+
public void TestNoCustomHttpClient() throws IOException {
35+
// before
36+
CoreConfiguration conf =
37+
new CoreConfiguration().serviceAccountKey(TestUtils.MOCK_SERVICE_ACCOUNT_KEY);
38+
39+
// when
40+
ApiClient apiClient = new ApiClient(conf);
41+
42+
// then
43+
/*
44+
* verify a fresh OkHttpClient got created which will have the auth header set
45+
* by the {@link cloud.stackit.sdk.core.KeyFlowAuthenticator}
46+
*/
47+
OkHttpClient httpClient = new OkHttpClient();
48+
Authenticator authenticator =
49+
new KeyFlowAuthenticator(httpClient, conf, SetupAuth.setupKeyFlow(conf));
50+
httpClient = httpClient.newBuilder().authenticator(authenticator).build();
51+
52+
Assertions.assertNotNull(apiClient.getHttpClient());
53+
Assertions.assertEquals(
54+
httpClient.authenticator().getClass(),
55+
apiClient.getHttpClient().authenticator().getClass());
56+
}
57+
}

0 commit comments

Comments
 (0)